From 4064493561d3e07f78ae79016310bd64c30f12a3 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Fri, 8 Dec 2023 14:01:46 +0200 Subject: [PATCH] feat: capability to send HTML emails --- .../commands/check_drafts_to_delete.py | 25 +++---- backend/benefit/helsinkibenefit/settings.py | 7 +- .../benefit/locale/en/LC_MESSAGES/django.po | 5 +- .../benefit/locale/fi/LC_MESSAGES/django.po | 10 ++- .../benefit/locale/sv/LC_MESSAGES/django.po | 10 ++- .../benefit/messages/automatic_messages.py | 65 ++++++++++++++++--- backend/benefit/messages/tests/test_api.py | 13 +++- backend/benefit/messages/views.py | 21 +++++- 8 files changed, 125 insertions(+), 31 deletions(-) diff --git a/backend/benefit/applications/management/commands/check_drafts_to_delete.py b/backend/benefit/applications/management/commands/check_drafts_to_delete.py index 8f5cc077e0..1a05e0380f 100644 --- a/backend/benefit/applications/management/commands/check_drafts_to_delete.py +++ b/backend/benefit/applications/management/commands/check_drafts_to_delete.py @@ -2,12 +2,15 @@ from django.core.management.base import BaseCommand from django.utils import timezone -from django.utils.text import format_lazy from django.utils.translation import gettext_lazy as _ from applications.enums import ApplicationStatus from applications.models import Application -from messages.automatic_messages import send_email_to_applicant +from messages.automatic_messages import ( + get_email_template_context, + render_email_template, + send_email_to_applicant, +) APPLICATION_ABOUT_TO_BE_DELETED_MESSAGE = _( "Your application {id} will be deleted soon. If you want to continue the application process, please do so by " @@ -65,14 +68,14 @@ def notify_applications(days_to_deletion: int, days_to_keep: int) -> int: def _send_notification_mail(application: Application, days_to_deletion: int) -> int: """Send a notification mail to the applicant about the upcoming application deletion""" - subject = _("Your application is about to be deleted") + application_deletion_date = ( + application.modified_at + timedelta(days=days_to_deletion) + ).strftime("%d.%m.%Y") + context = get_email_template_context(application) + context["application_deletion_date"] = application_deletion_date - message = format_lazy( - APPLICATION_ABOUT_TO_BE_DELETED_MESSAGE, - id=application.application_number, - application_deletion_date=( - application.modified_at + timedelta(days=days_to_deletion) - ).strftime("%d.%m.%Y"), - ) + subject = _("Your Helsinki benefit draft application will expire") + message = render_email_template(context, "draft-notice", "txt") + html_message = render_email_template(context, "draft-notice", "html") - return send_email_to_applicant(application, subject, message) + return send_email_to_applicant(application, subject, message, html_message) diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 2ed11f658b..a07ed2ea10 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -272,10 +272,15 @@ "simple_history.middleware.HistoryRequestMiddleware", ] +MJML_COMPILED_EMAIL_TEMPLATE_PATHS = [ + os.path.join(BASE_DIR, "helsinkibenefit/templates/emails/mjml-generated/html"), + os.path.join(BASE_DIR, "helsinkibenefit/templates/emails/mjml-generated/txt"), +] + TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [] + MJML_COMPILED_EMAIL_TEMPLATE_PATHS, "APP_DIRS": True, "OPTIONS": { "context_processors": [ diff --git a/backend/benefit/locale/en/LC_MESSAGES/django.po b/backend/benefit/locale/en/LC_MESSAGES/django.po index a19abf59e3..fe7b898ff9 100644 --- a/backend/benefit/locale/en/LC_MESSAGES/django.po +++ b/backend/benefit/locale/en/LC_MESSAGES/django.po @@ -650,7 +650,7 @@ msgid "" "processed." msgstr "" -msgid "You have received a new message from Helsinki benefit" +msgid "You have received a new message regarding your Helsinki benefit application" msgstr "" #, python-format @@ -819,3 +819,6 @@ msgstr "The Helsinki Benefit card" msgid "employee_consent" msgstr "Employee's consent for processing personal data" + +msgid "Your Helsinki benefit application requires additional information" +msgstr "" \ No newline at end of file diff --git a/backend/benefit/locale/fi/LC_MESSAGES/django.po b/backend/benefit/locale/fi/LC_MESSAGES/django.po index 7461b8077a..026be54f68 100644 --- a/backend/benefit/locale/fi/LC_MESSAGES/django.po +++ b/backend/benefit/locale/fi/LC_MESSAGES/django.po @@ -660,8 +660,8 @@ msgstr "" "{additional_information_needed_by} mennessä, muuten hakemusta ei voida " "käsitellä." -msgid "You have received a new message from Helsinki benefit" -msgstr "Olet saanut uuden viestin Helsinki-lisä -hakemukseen" +msgid "You have received a new message regarding your Helsinki benefit application" +msgstr "Olet saanut uuden viestin Helsinki-lisä -hakemukseen littyen" #, python-format msgid "" @@ -989,3 +989,9 @@ msgstr "Ei myönnetty" msgid "Printed at" msgstr "Tulostettu" + +msgid "Your Helsinki benefit draft application will expire" +msgstr "Helsinki-lisä -hakemuksesi luonnos vanhenee" + +msgid "Your Helsinki benefit application requires additional information" +msgstr "Helsinki-lisä -hakemus tarvitsee lisätietoja" \ No newline at end of file diff --git a/backend/benefit/locale/sv/LC_MESSAGES/django.po b/backend/benefit/locale/sv/LC_MESSAGES/django.po index a5d736daec..d39a55e82c 100644 --- a/backend/benefit/locale/sv/LC_MESSAGES/django.po +++ b/backend/benefit/locale/sv/LC_MESSAGES/django.po @@ -657,8 +657,8 @@ msgstr "" "Din ansökan har öppnats för redigering. Gör ändringarna senast " "{additional_information_needed_by}, annars kan ansökan inte behandlas." -msgid "You have received a new message from Helsinki benefit" -msgstr "Du har fått ett nytt meddelande om Helsingforstilläg" +msgid "You have received a new message regarding your Helsinki benefit application" +msgstr "Du har fått ett nytt meddelande om ansökningen om Helsingforstillägget" #, python-format msgid "" @@ -981,3 +981,9 @@ msgstr "Inget stöd" msgid "Printed at" msgstr "Tryckt" + +msgid "Your Helsinki benefit draft application will expire" +msgstr "Ditt utkast för ansökan går ut" + +msgid "Your Helsinki benefit application requires additional information" +msgstr "Ansökan om Helsingforstillägget behöver ytterligare information" \ No newline at end of file diff --git a/backend/benefit/messages/automatic_messages.py b/backend/benefit/messages/automatic_messages.py index f14cc1882c..31c3a54ce9 100644 --- a/backend/benefit/messages/automatic_messages.py +++ b/backend/benefit/messages/automatic_messages.py @@ -1,8 +1,10 @@ +import datetime import logging from smtplib import SMTPException from django.conf import settings from django.core.mail import send_mail +from django.template.loader import render_to_string from django.utils import translation from django.utils.text import format_lazy from django.utils.translation import gettext_lazy as _ @@ -19,10 +21,12 @@ ) -def _message_notification_email_subject(): +def default_email_notification_subject(): # force evaluation of lazy string so that the messages in local memory queue remain translated # correctly during unit tests - return str(_("You have received a new message from Helsinki benefit")) + return str( + _("You have received a new message regarding your Helsinki benefit application") + ) def _message_notification_email_body(application): @@ -46,8 +50,31 @@ def _message_notification_email_body(application): ) +def get_email_template_context(application: Application): + year = datetime.date.today().year + + return { + "current_year": year, + "application": { + "created_at": application.created_at, + "application_number": application.application_number, + }, + "language": application.applicant_language, + } + + +def render_email_template( + email_context: dict, template_name: str, template_type: str = "txt" +): + lang = email_context["language"] or "fi" + return render_to_string(f"{template_name}_{lang}.{template_type}", email_context) + + def send_email_to_applicant( - application: Application, subject: str = None, message: str = None + application: Application, + subject: str = None, + text_message: str = None, + html_message: str = None, ) -> int: """ :param application: The application being reopened @@ -64,10 +91,11 @@ def send_email_to_applicant( with translation.override(application.applicant_language): try: return send_mail( - subject=subject if subject else _message_notification_email_subject(), - message=message - if message + subject=subject if subject else default_email_notification_subject(), + message=text_message + if text_message else _message_notification_email_body(application), + html_message=html_message or None, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[application.company_contact_person_email], fail_silently=False, @@ -94,6 +122,9 @@ def send_application_reopened_message( :param application: The application being reopened :param additional_information_needed_by: The date by which the applicant must provide the additional information """ + + formatted_info_needed_by = additional_information_needed_by.strftime("%d.%m.%Y") + with translation.override(application.applicant_language): Message.objects.create( sender=user, @@ -101,9 +132,23 @@ def send_application_reopened_message( message_type=MessageType.HANDLER_MESSAGE, content=format_lazy( APPLICATION_REOPENED_MESSAGE, - additional_information_needed_by=additional_information_needed_by.strftime( - "%d.%m.%Y" - ), + additional_information_needed_by=formatted_info_needed_by, ), ) - send_email_to_applicant(application) + + context = get_email_template_context(application) + context["additional_information_deadline_date"] = formatted_info_needed_by + context["additional_information_deadline_time"] = "23:00" + + message = render_email_template( + context, "additional-information-required", "txt" + ) + html_message = render_email_template( + context, "additional-information-required", "html" + ) + send_email_to_applicant( + application, + _("Your Helsinki benefit application requires additional information"), + message, + html_message, + ) diff --git a/backend/benefit/messages/tests/test_api.py b/backend/benefit/messages/tests/test_api.py index 69d1f59edb..d48a78f031 100644 --- a/backend/benefit/messages/tests/test_api.py +++ b/backend/benefit/messages/tests/test_api.py @@ -1,3 +1,4 @@ +import email import uuid from copy import deepcopy @@ -322,12 +323,20 @@ def test_create_message( assert_email_subject_language(str(mailoutbox[0].subject), email_language) assert_email_body_language(str(mailoutbox[0].body), email_language) if email_language == "fi": - assert "Olet saanut uuden viestin" in mailoutbox[0].subject - assert "on tullut uusi viesti" in mailoutbox[0].body assert mailoutbox[0].to == [ handling_application.company_contact_person_email ] assert mailoutbox[0].from_email == settings.DEFAULT_FROM_EMAIL + assert "Olet saanut uuden viestin" in mailoutbox[0].subject + + # Parse email to an object and assert that it contains the correct text content + email_parts = email.message_from_string(mailoutbox[0].body) + for part in email_parts.walk(): + if part.get_content_type() == "text/plain": + assert ( + "Olet saanut uuden viestin Helsinki-lisä -hakemukseen littyen. Voit lukea viestin" + in part.get_payload() + ) assert result.status_code == 201 message_qs = Message.objects.filter(message_type=msg_type) diff --git a/backend/benefit/messages/views.py b/backend/benefit/messages/views.py index fdeb18b227..c05fc2c457 100755 --- a/backend/benefit/messages/views.py +++ b/backend/benefit/messages/views.py @@ -1,12 +1,18 @@ from django.conf import settings from django.db import transaction +from django.utils import translation from django.utils.translation import gettext_lazy as _ from rest_framework import viewsets from rest_framework.exceptions import NotFound from applications.models import Application from common.permissions import BFIsApplicant, BFIsHandler, TermsOfServiceAccepted -from messages.automatic_messages import send_email_to_applicant +from messages.automatic_messages import ( + default_email_notification_subject, + get_email_template_context, + render_email_template, + send_email_to_applicant, +) from messages.models import Message, MessageType from messages.permissions import HasMessagePermission from messages.serializers import MessageSerializer, NoteSerializer @@ -48,12 +54,23 @@ class HandlerMessageViewSet(ApplicantMessageViewSet): def perform_create(self, serializer): message = serializer.save() + application = message.application # Never send email if it's a handler's note! if message.message_type in [ MessageType.HANDLER_MESSAGE, MessageType.APPLICANT_MESSAGE, ]: - send_email_to_applicant(message.application) + with translation.override(application.applicant_language): + subject = default_email_notification_subject() + context = get_email_template_context(application) + text_message = render_email_template(context, "received-message", "txt") + html_message = render_email_template( + context, "received-message", "html" + ) + + send_email_to_applicant( + application, subject, text_message, html_message + ) def get_queryset(self): try: