From f144c5174f65ada812f05aac42fb6268626e24df Mon Sep 17 00:00:00 2001 From: Andreas Ellewsen <ae@uio.no> Date: Tue, 2 Nov 2021 14:29:57 +0100 Subject: [PATCH] Introduce Email Templates model Introduced to make editing of templates possible using the admin interface. There are currently two types of templates, and there can only be one instance of each type so that the system knows which template to use in which situation. Resolves: GREG-73 --- gregui/admin.py | 8 ++- gregui/mailutils.py | 79 ++++++++++++++++--------- gregui/migrations/0002_emailtemplate.py | 48 +++++++++++++++ gregui/models.py | 32 ++++++++++ gregui/tests/conftest.py | 32 +++++++++- gregui/tests/test_mailutils.py | 34 +++++++---- 6 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 gregui/migrations/0002_emailtemplate.py diff --git a/gregui/admin.py b/gregui/admin.py index d5ea59b0..7c3f7717 100644 --- a/gregui/admin.py +++ b/gregui/admin.py @@ -1,11 +1,17 @@ from django.contrib import admin from reversion.admin import VersionAdmin -from gregui.models import GregUserProfile +from gregui.models import EmailTemplate, GregUserProfile class GregUserProfileAdmin(VersionAdmin): pass +class EmailTemplateAdmin(VersionAdmin): + list_display = ["id", "template_key", "subject", "from_email"] + save_as = True + + admin.site.register(GregUserProfile, GregUserProfileAdmin) +admin.site.register(EmailTemplate, EmailTemplateAdmin) diff --git a/gregui/mailutils.py b/gregui/mailutils.py index 19795179..9ccbe5e3 100644 --- a/gregui/mailutils.py +++ b/gregui/mailutils.py @@ -1,41 +1,64 @@ +from typing import Union from django.conf import settings -from django.template.loader import render_to_string +from django.template.context import Context from django_q.tasks import async_task +from gregui.models import EmailTemplate -def registration_template(institution, sponsor) -> str: - keywords = { - "institution": institution, - "sponsor": sponsor, - "registration_link": "www.google.com", - } - return render_to_string("guest_registration.txt", keywords) +def prepare_arguments( + template: EmailTemplate, context: dict[str, str], mail_to: str +) -> dict[str, Union[str, list[str]]]: + """Combine input to a dict ready for use as arguments ti django's send_mail""" + return { + "subject": template.get_subject(context), + "message": template.get_body(context), + "from_email": template.from_email or None, + "recipient_list": [mail_to], + } -def confirmation_template(guest) -> str: - keywords = {"guest": guest, "confirmation_link": "www.google.com"} - return render_to_string("sponsor_confirmation.txt", keywords) +def registration_template( + institution: str, sponsor: str, mail_to: str +) -> dict[str, Union[str, list[str]]]: + """ + Prepare email for registration -def send_registration_mail(mail_to, sponsor) -> str: - return async_task( - "django.core.mail.send_mail", - **{ - "subject": "Subject", - "message": registration_template(settings.INSTANCE_NAME, sponsor), - "from_email": None, - "recipient_list": [mail_to], + Produces a complete set of arguments ready for use with django.core.mail.send_mail + when sending a registration email to the guest. + """ + template = EmailTemplate.objects.get( + template_key=EmailTemplate.EmailType.GUEST_REGISTRATION + ) + context = Context( + { + "institution": institution, + "sponsor": sponsor, + "registration_link": "www.google.com", } ) + return prepare_arguments(template, context, mail_to) -def send_confirmation_mail(mail_to, guest) -> str: - return async_task( - "django.core.mail.send_mail", - **{ - "subject": "Subject", - "message": confirmation_template(guest), - "from_email": None, - "recipient_list": [mail_to], - } +def confirmation_template(guest: str, mail_to: str) -> dict[str, Union[str, list[str]]]: + """ + Prepare email for confirmation + + Produces a complete set of arguments ready for use with django.core.mail.send_mail + when sending a confirmation email to the sponsor. + """ + template = EmailTemplate.objects.get( + template_key=EmailTemplate.EmailType.SPONSOR_CONFIRMATION ) + context = Context({"guest": guest, "confirmation_link": "www.google.com"}) + return prepare_arguments(template, context, mail_to) + + +def send_registration_mail(mail_to: str, sponsor: str) -> str: + arguments = registration_template(settings.INSTANCE_NAME, sponsor, mail_to) + return async_task("django.core.mail.send_mail", **arguments) + + +def send_confirmation_mail(mail_to: str, guest: str) -> str: + arguments = confirmation_template(guest, mail_to) + return async_task("django.core.mail.send_mail", **arguments) diff --git a/gregui/migrations/0002_emailtemplate.py b/gregui/migrations/0002_emailtemplate.py new file mode 100644 index 00000000..15c4b7b6 --- /dev/null +++ b/gregui/migrations/0002_emailtemplate.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.8 on 2021-11-02 12:49 + +import dirtyfields.dirtyfields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregui", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="EmailTemplate", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "template_key", + models.CharField( + choices=[ + ("guest_registration", "Guest Registration"), + ("sponsor_confirmation", "Sponsor Confirmation"), + ], + max_length=64, + unique=True, + ), + ), + ("subject", models.CharField(blank=True, max_length=255, null=True)), + ("from_email", models.CharField(blank=True, max_length=255, null=True)), + ("body", models.TextField(blank=True, null=True)), + ], + options={ + "abstract": False, + }, + bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model), + ), + ] diff --git a/gregui/models.py b/gregui/models.py index 48b579be..551b20f8 100644 --- a/gregui/models.py +++ b/gregui/models.py @@ -1,3 +1,4 @@ +from django import template from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy @@ -24,3 +25,34 @@ class GregUserProfile(BaseModel): null=True, ) userid_feide = models.CharField(gettext_lazy("userid-feide"), max_length=150) + + +class EmailTemplate(BaseModel): + """ + Stores templates for emails. + + Only one template of each type is allowed. To introduce new ones, simply add a new + EmailType. + """ + + class EmailType(models.TextChoices): + """Types of Emails""" + + GUEST_REGISTRATION = "guest_registration" + SPONSOR_CONFIRMATION = "sponsor_confirmation" + + template_key = models.CharField( + max_length=64, choices=EmailType.choices, unique=True + ) + subject = models.CharField(max_length=255, blank=True, null=True) + from_email = models.CharField(max_length=255, blank=True, null=True) + body = models.TextField(blank=True, null=True) + + def get_rendered_template(self, tpl, context): + return template.Template(tpl).render(context) + + def get_subject(self, context): + return self.get_rendered_template(self.subject, context) + + def get_body(self, context): + return self.get_rendered_template(self.body, context) diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py index fc70bdca..2e34c790 100644 --- a/gregui/tests/conftest.py +++ b/gregui/tests/conftest.py @@ -19,7 +19,7 @@ from greg.models import ( Sponsor, ) -from gregui.models import GregUserProfile +from gregui.models import EmailTemplate, GregUserProfile # faker spams the logs with localisation warnings # see https://github.com/joke2k/faker/issues/753 @@ -256,3 +256,33 @@ def log_in(client): return client return _log_in + + +@pytest.fixture +def confirmation_template(): + et = EmailTemplate.objects.create( + template_key=EmailTemplate.EmailType.SPONSOR_CONFIRMATION, + subject="confirmation subject", + body="""Dette er en automatisk generert melding fra gjesteregistreringstjenesten. +Din gjest, {{ guest }}, har fullført registrering, bekreft gjesten her: {{ confirmation_link }} + +This message has been automatically generated by the guest registration system. +Your guest, {{ guest }}, has completed their registration, please confirm the guest here: {{ confirmation_link }}""", + ) + return EmailTemplate.objects.get(id=et.id) + + +@pytest.fixture +def registration_template(): + et = EmailTemplate.objects.create( + template_key=EmailTemplate.EmailType.GUEST_REGISTRATION, + subject="registration subject", + body="""Dette er en automatisk generert melding fra gjesteregistreringstjenesten. +Du har blitt registrert som gjest på {{ institution }} av {{ sponsor }}. +For å fullføre registreringen av gjestekontoen følg denne lenken: {{ registration_link }} + +This message has been automatically generated by the guest registration system. +You have been registered as a guest at {{ institution }} by {{ sponsor }}. +To complete the registration of your guest account, please follow this link: {{ registration_link }}""", + ) + return EmailTemplate.objects.get(id=et.id) diff --git a/gregui/tests/test_mailutils.py b/gregui/tests/test_mailutils.py index 03f30f3d..7977f4cf 100644 --- a/gregui/tests/test_mailutils.py +++ b/gregui/tests/test_mailutils.py @@ -6,33 +6,43 @@ from gregui import mailutils @pytest.mark.django_db -def test_registration_template(): - prefilled_template = """Dette er en automatisk generert melding fra gjesteregistreringstjenesten. +def test_registration_template(registration_template): + prefilled_template = { + "from_email": None, + "recipient_list": ["test@example.com"], + "subject": "registration subject", + "message": """Dette er en automatisk generert melding fra gjesteregistreringstjenesten. Du har blitt registrert som gjest på InstanceName av Foo Bar. For å fullføre registreringen av gjestekontoen følg denne lenken: www.google.com This message has been automatically generated by the guest registration system. You have been registered as a guest at InstanceName by Foo Bar. -To complete the registration of your guest account, please follow this link: www.google.com -""" - rendered_template = mailutils.registration_template("InstanceName", "Foo Bar") +To complete the registration of your guest account, please follow this link: www.google.com""", + } + rendered_template = mailutils.registration_template( + "InstanceName", "Foo Bar", "test@example.com" + ) assert rendered_template == prefilled_template @pytest.mark.django_db -def test_confirmation_template(): - prefilled_template = """Dette er en automatisk generert melding fra gjesteregistreringstjenesten. +def test_confirmation_template(confirmation_template): + prefilled_template = { + "from_email": None, + "recipient_list": ["test@example.com"], + "subject": "confirmation subject", + "message": """Dette er en automatisk generert melding fra gjesteregistreringstjenesten. Din gjest, Foo Bar, har fullført registrering, bekreft gjesten her: www.google.com This message has been automatically generated by the guest registration system. -Your guest, Foo Bar, has completed their registration, please confirm the guest here: www.google.com -""" - rendered_template = mailutils.confirmation_template("Foo Bar") +Your guest, Foo Bar, has completed their registration, please confirm the guest here: www.google.com""", + } + rendered_template = mailutils.confirmation_template("Foo Bar", "test@example.com") assert rendered_template == prefilled_template @pytest.mark.django_db -def test_registration_mail(): +def test_registration_mail(registration_template): mail.outbox = [] task_id = mailutils.send_registration_mail("test@example.no", "Foo") assert result(task_id) == 1 @@ -41,7 +51,7 @@ def test_registration_mail(): @pytest.mark.django_db -def test_confirmation_mail(): +def test_confirmation_mail(confirmation_template): mail.outbox = [] task_id = mailutils.send_confirmation_mail("test@example.no", "Foo") assert result(task_id) == 1 -- GitLab