diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8af89154dd1438a738646e5ee015292fa317b460..3f8ab898b573438cda321888fa8f70ccc7e483d3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,7 @@ Added * Views filtering for person names now also search the username of a linked user * OAuth2 applications now take an icon which is shown in the authorization progress. * Add support for hiding the main side nav in ``base.html``. +* Provide base template and function for sending emails with a template. Fixed ~~~~~ @@ -51,6 +52,7 @@ Changed to authorize an external application. * Tables can be scrolled horizontally. * Overhauled person detail page +* Use common base template for all emails. `2.5`_ – 2022-01-02 ------------------- diff --git a/aleksis/core/celery.py b/aleksis/core/celery.py index 10457ea9b84871da3038cf4712babb601456bfcd..ab78cfb080ad1abc79f92e873641ef36c22432c9 100644 --- a/aleksis/core/celery.py +++ b/aleksis/core/celery.py @@ -1,12 +1,13 @@ import os +from traceback import format_exception from django.conf import settings from celery import Celery from celery.signals import task_failure -from templated_email import send_templated_mail from .util.core_helpers import get_site_preferences +from .util.email import send_email os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings") @@ -17,10 +18,10 @@ app.autodiscover_tasks() @task_failure.connect def task_failure_notifier( - sender=None, task_id=None, exception=None, args=None, traceback=None, **kwargs + sender=None, task_id=None, exception=None, args=None, kwargs=None, traceback=None, **__ ): recipient_list = [e[1] for e in settings.ADMINS] - send_templated_mail( + send_email( template_name="celery_failure", from_email=get_site_preferences()["mail__address"], recipient_list=recipient_list, @@ -29,8 +30,8 @@ def task_failure_notifier( "task": str(sender), "task_id": str(task_id), "exception": str(exception), - "args": str(args), - "kwargs": str(kwargs), - "traceback": str(traceback), + "args": args, + "kwargs": kwargs, + "traceback": "".join(format_exception(type(exception), exception, traceback)), }, ) diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py index 234c82c0ca12b46c7e1aebd7151fb2f2a1c0c2ca..e077f4ccd6175837eedce1814778341fe2772b66 100644 --- a/aleksis/core/data_checks.py +++ b/aleksis/core/data_checks.py @@ -8,10 +8,10 @@ from django.utils.translation import gettext as _ import reversion from reversion import set_comment -from templated_email import send_templated_mail from .util.celery_progress import ProgressRecorder, recorded_task from .util.core_helpers import get_site_preferences +from .util.email import send_email class SolveOption: @@ -274,9 +274,8 @@ def send_emails_for_data_checks(): for group in get_site_preferences()["general__data_checks_recipient_groups"]: recipient_list += [p.mail_sender for p in group.announcement_recipients if p.email] - send_templated_mail( + send_email( template_name="data_checks", - from_email=get_site_preferences()["mail__address"], recipient_list=recipient_list, context={"results": results_with_checks}, ) diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 9c78211eac9dfabe6e1d0b8de8e2034f0dc9da2c..8bb6def6cc619e6729e73b4760ec329b77c46a07 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -45,7 +45,6 @@ from oauth2_provider.models import ( ) from phonenumber_field.modelfields import PhoneNumberField from polymorphic.models import PolymorphicModel -from templated_email import send_templated_mail from aleksis.core.data_checks import BrokenDashboardWidgetDataCheck, DataCheck, DataCheckRegistry @@ -65,6 +64,7 @@ from .mixins import ( ) from .tasks import send_notification from .util.core_helpers import generate_random_code, get_site_preferences, now_tomorrow +from .util.email import send_email from .util.model_helpers import ICONS FIELD_CHOICES = ( @@ -386,7 +386,7 @@ class Person(ExtensibleModel): recipients = recipients or [ get_site_preferences()["account__person_change_notification_contact"] ] - send_templated_mail( + send_email( template_name="person_changed", from_email=self.mail_sender_via, headers={ diff --git a/aleksis/core/templates/templated_email/base.email b/aleksis/core/templates/templated_email/base.email new file mode 100644 index 0000000000000000000000000000000000000000..cce8e7a2fdfc6a005bec62715b8b7646e26b1cea --- /dev/null +++ b/aleksis/core/templates/templated_email/base.email @@ -0,0 +1,21 @@ +{% load i18n %} + +{% block subject %}[{{ SITE_PREFERENCES.general__title }}] {% block subject_content %}{% endblock %}{% endblock %} + +{% block plain %}{% block plain_greeting %}{% trans "Hello" %},{% endblock %} +{% block plain_content %}{% endblock %} +- {{ SITE_PREFERENCES.general__title }} +{% endblock %} + +{% block html %} + <style> + {% include "templated_email/email.css" %} + </style> + <body> + <div class="main"> + {% block html_greeting %}<p>{% trans "Hello" %},</p>{% endblock %} + {% block html_content %}{% endblock %} + <p>- {{ SITE_PREFERENCES.general__title }}</p> + </div> + </body> +{% endblock %} diff --git a/aleksis/core/templates/templated_email/celery_failure.email b/aleksis/core/templates/templated_email/celery_failure.email index d355823eb70e58f393c6a9006ed2045ac1882171..8718b6f522dfdca868abfbaaae9a6e5032983846 100644 --- a/aleksis/core/templates/templated_email/celery_failure.email +++ b/aleksis/core/templates/templated_email/celery_failure.email @@ -1,44 +1,50 @@ +{% extends "templated_email/base.email" %} {% load i18n %} -{% block subject %} - {% blocktrans with task_name=task_name%} Celery task {{ task_name }} failed!{% endblocktrans %} -{% endblock %} +{% block subject_content %}{% blocktrans with task_name=task_name %}Celery task {{ task_name }} failed!{% endblocktrans %}{% endblock %} -{% block plain %} - {% trans "Hello," %} - {% blocktrans with task_name=task_name %} - the celery task {{ task_name }} failed with following information: - {% endblocktrans %} +{% block plain_content %} +{% blocktrans with task_name=task_name %}the celery task {{ task_name }} failed with following information:{% endblocktrans %} + * {% trans "Task" %}: {{ task_name }} + * {% trans "Task ID" %}: {{ task_id }} + * {% trans "Raised exception" %}: {{ exception }} + * {% trans "Positional arguments" %}: + {% for arg in args %}- {{ arg }} + {% endfor %} + * {% trans "Keyword arguments" %}: + {% for key, value in kwargs.items %}- {{ key }}: {{ value }} + {% endfor %} +{{ traceback }}{% endblock %} -{% blocktrans with task_name=task_name task=task task_id=task_id exception=exception args=args kwargs=kwargs traceback=traceback %} - * Task name: {{task_name}} - * Task: {{task}} - * Id of the task: {{task_id}} - * Exception instance raised: {{ exception }} - * Positional arguments the task was called with: {{ args }} - * Keyword arguments the task was called with: {{ kwargs }} - * Stack trace object: {{ traceback }} -{% endblocktrans %} -{% endblock %} +{% block html_content %} + <p> + {% blocktrans with task_name=task_name %} + the celery task {{ task_name }} failed with following information: + {% endblocktrans %} + </p> -{% block html %} - <p>{% trans "Hello," %}</p> - <p> - {% blocktrans with task_name=task_name %} - the celery task {{ task_name }} failed with following information: - {% endblocktrans %} - </p> + <ul> + <li>{% trans "Task" %}: {{ task_name }}</li> + <li>{% trans "Task ID" %}: {{ task_id }}</li> + <li>{% trans "Raised exception" %}: {{ exception }}</li> + <li>{% trans "Positional arguments" %}: + <ol> + {% for arg in args %} + <li>{{ arg }}</li> + {% endfor %} + </ol> + </li> + <li>{% trans "Keyword arguments" %}: + <ul> + {% for key, value in kwargs.items %} + <li>{{ key }}: {{ value }}</li> + {% endfor %} + </ul> + </li> + </ul> - <ul> - {% blocktrans with task_name=task_name task=task task_id=task_id exception=exception args=args kwargs=kwargs traceback=traceback %} - <li>Task name: {{task_name}}</li> - <li>Task: {{task}}</li> - <li>Id of the task: {{task_id}}</li> - <li>Exception instance raised: {{ exception }}</li> - <li>Positional arguments the task was called with: {{ args }}</li> - <li>Keyword arguments the task was called with: {{ kwargs }}</li> - <li>Stack trace object: {{ traceback }}</li> - </ul> - {% endblocktrans %} + <code> + {{ traceback|linebreaksbr }} + </code> {% endblock %} diff --git a/aleksis/core/templates/templated_email/data_checks.css b/aleksis/core/templates/templated_email/data_checks.css deleted file mode 100644 index b385b185ecfe66dcca070e8eb024bbb091917f04..0000000000000000000000000000000000000000 --- a/aleksis/core/templates/templated_email/data_checks.css +++ /dev/null @@ -1,16 +0,0 @@ -body { - line-height: 1.5; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - font-weight: normal; - color: rgba(0, 0, 0, 0.87); -} - -.count { - text-align: right; - font-family: monospace; - font-size: 14pt; -} - -td, th { - padding: 10px; -} diff --git a/aleksis/core/templates/templated_email/data_checks.email b/aleksis/core/templates/templated_email/data_checks.email index 0f9b5a5307fba9152683d4e03b0c41a2105629c7..1b3e04960596283327dbb5994fe6e8c6cae83b58 100644 --- a/aleksis/core/templates/templated_email/data_checks.email +++ b/aleksis/core/templates/templated_email/data_checks.email @@ -1,27 +1,16 @@ +{% extends "templated_email/base.email" %} {% load i18n %} +{% block subject_content %}{% trans "The system detected some new problems with your data." %}{% endblock %} -{% block subject %} - {% trans "The system detected some new problems with your data." %} -{% endblock %} - -{% block plain %} - {% trans "Hello," %} - - {% blocktrans %} - the system detected some new problems with your data. - Please take some time to inspect them and solve the issues or mark them as ignored. - {% endblocktrans %} +{% block plain_content %} +{% blocktrans %}the system detected some new problems with your data. +Please take some time to inspect them and solve the issues or mark them as ignored.{% endblocktrans %} - {% for result in results %} - {{ result.0.problem_name }}: {{ result.1 }} - {% endfor %} +{% for result in results %}- {{ result.0.problem_name }}: {{ result.1 }} +{% endfor %} {% endblock %} -{% block html %} - <style> - {% include "templated_email/data_checks.css" %} - </style> - <p>{% trans "Hello," %}</p> +{% block html_content %} <p> {% blocktrans %} the system detected some new problems with your data. diff --git a/aleksis/core/templates/templated_email/email.css b/aleksis/core/templates/templated_email/email.css new file mode 100644 index 0000000000000000000000000000000000000000..8cd112624c0c0b78f663027841895f4e07ce608b --- /dev/null +++ b/aleksis/core/templates/templated_email/email.css @@ -0,0 +1,41 @@ +body { + line-height: 1.5; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-weight: normal; + color: rgba(0, 0, 0, 0.87); + display: flex; + justify-content: center; + align-items: center; +} + +table, tr { + width: 100%; +} + +.main { + max-width: 700px; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-transition: -webkit-box-shadow .25s; + transition: -webkit-box-shadow .25s; + transition: box-shadow .25s; + transition: box-shadow .25s, -webkit-box-shadow .25s; + border-radius: 2px; + background-color: #fff; + margin: 30px; + padding: 20px; +} + +.first th { + border-bottom: 1px solid; +} + + +td, th { + padding-left: 5px; + padding-right: 5px; +} + +.align-center { + text-align: center; +} diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email index 68a3e79570d1ba93013f2287b6d96cf8679a3572..e1290238f9713829640e6ebc518a618cc61d762f 100644 --- a/aleksis/core/templates/templated_email/notification.email +++ b/aleksis/core/templates/templated_email/notification.email @@ -1,31 +1,25 @@ +{% extends "templated_email/base.email" %} {% load i18n %} -{% block subject %} {% trans "New notification for" %} {{ notification_user }} {% endblock %} +{% block subject_content %}{% trans "New notification for" %} {{ notification_user }}{% endblock %} -{% block plain %} - {% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %} +{% block plain_greeting %}{% blocktrans with notification_user=notification_user %}Hello {{ notification_user }},{% endblocktrans %}{% endblock %} - {% trans "we got a new notification for you:" %} +{% block plain_content %} +{% trans "we got a new notification for you:" %} - {{ notification.title }} +{{ notification.title }} - {{ notification.description }} +{{ notification.description }}{% if notification.link %} - {% if notification.link %} - {% trans "More information" %} → {{ notification.link }} - {% endif %} +{% trans "More information" %} → {{ notification.link }}{% endif %} - {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %} - Sent by {{ trans_sender }} at {{ trans_created_at }} - {% endblocktrans %} - - {% trans "Your AlekSIS team" %} +{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}Sent by {{ trans_sender }} at {{ trans_created_at }}{% endblocktrans %} {% endblock %} -{% block html %} -<main> - <p>{% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}</p> +{% block html_greeting %}<p>{% blocktrans with notification_user=notification_user %}Hello {{ notification_user }},{% endblocktrans %}</p>{% endblock %} +{% block html_content %} <p>{% trans "we got a new notification for you:" %}</p> <blockquote> @@ -41,9 +35,4 @@ Sent by {{ trans_sender }} at {{ trans_created_at }} {% endblocktrans %} </p> - - <p> - <i>{% trans "Your AlekSIS team" %}</i> - </p> -</main> {% endblock %} diff --git a/aleksis/core/templates/templated_email/person_changed.email b/aleksis/core/templates/templated_email/person_changed.email index 1e0d8be25684486bfdceac0b979b461253c21df7..068db0cc67f2b6f55e0f15a5d3e80f06a29594b4 100644 --- a/aleksis/core/templates/templated_email/person_changed.email +++ b/aleksis/core/templates/templated_email/person_changed.email @@ -1,23 +1,15 @@ {% load i18n %} +{% extends "templated_email/base.email" %} +{% block subject_content %}{% blocktrans with person=person %}{{ person }} changed their data!{% endblocktrans %}{% endblock %} -{% block subject %} - {% blocktrans with person=person %}{{ person }} changed their data!{% endblocktrans %} -{% endblock %} - -{% block plain %} - {% trans "Hello," %} - - {% blocktrans with person=person %} - the person {{ person }} recently changed the following fields: - {% endblocktrans %} +{% block plain_content %} +{% blocktrans with person=person %}the person {{ person }} recently changed the following fields:{% endblocktrans %} - {% for field in changed_fields %} - * {{ field }} - {% endfor %} +{% for field in changed_fields %}* {{ field }} +{% endfor %} {% endblock %} {% block html %} - <p>{% trans "Hello," %}</p> <p> {% blocktrans with person=person %} the person {{ person }} recently changed the following fields: diff --git a/aleksis/core/util/email.py b/aleksis/core/util/email.py new file mode 100644 index 0000000000000000000000000000000000000000..fc7525a7d64e50ec1045dacf02d253ed13b17568 --- /dev/null +++ b/aleksis/core/util/email.py @@ -0,0 +1,30 @@ +from typing import Any, Dict, List, Optional + +from django.conf import settings + +from templated_email import send_templated_mail + +from aleksis.core.util.core_helpers import get_site_preferences, process_custom_context_processors + + +def send_email( + template_name: str, + recipient_list: List[str], + context: Dict[str, Any], + from_email: Optional[str] = None, + **kwargs, +): + """Send templated email with data from context processors.""" + processed_context = process_custom_context_processors(settings.PDF_CONTEXT_PROCESSORS) + processed_context.update(context) + if not from_email: + from_address = get_site_preferences()["mail__address"] + from_name = get_site_preferences()["general__title"] + from_email = f"{from_name} <{from_address}>" + return send_templated_mail( + template_name=template_name, + from_email=from_email, + recipient_list=recipient_list, + context=processed_context, + **kwargs, + ) diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py index dac344b7a2e2877d65ff1bc80b580b860bf1067f..13887a197defb9e24f5a9b1538561287b80c942f 100644 --- a/aleksis/core/util/notifications.py +++ b/aleksis/core/util/notifications.py @@ -8,9 +8,8 @@ from django.template.loader import get_template from django.utils.functional import lazy from django.utils.translation import gettext_lazy as _ -from templated_email import send_templated_mail - from .core_helpers import lazy_preference +from .email import send_email try: from twilio.rest import Client as TwilioClient @@ -35,9 +34,8 @@ def _send_notification_email(notification: "Notification", template: str = "noti "notification": notification, "notification_user": notification.recipient.addressing_name, } - send_templated_mail( + send_email( template_name=template, - from_email=lazy_preference("mail", "address"), recipient_list=[notification.recipient.email], context=context, )