diff --git a/aleksis/apps/alsijil/data_checks.py b/aleksis/apps/alsijil/data_checks.py index 54abf4678345b1bdb63596f19f31a4125e523cc1..679de00ee61914f8b4962a38cda5ba47f42a7bec 100644 --- a/aleksis/apps/alsijil/data_checks.py +++ b/aleksis/apps/alsijil/data_checks.py @@ -2,12 +2,14 @@ import logging from django.contrib.contenttypes.models import ContentType from django.db.models import F +from django.db.models.aggregates import Count from django.utils.translation import gettext as _ import reversion from calendarweek import CalendarWeek +from templated_email import send_templated_mail -from aleksis.core.util.core_helpers import celery_optional +from aleksis.core.util.core_helpers import celery_optional, get_site_preferences class SolveOption: @@ -97,3 +99,37 @@ def check_data(): for check in DATA_CHECKS: logging.info(f"Run check: {check.verbose_name}") check.check_data() + + if get_site_preferences()["alsijil__data_checks_send_emails"]: + send_emails_for_data_checks() + + +def send_emails_for_data_checks(): + """Notify one or more recipients about new problems with data. + + Recipients can be set in dynamic preferences. + """ + from .models import DataCheckResult # noqa + + results = DataCheckResult.objects.filter(solved=False, sent=False) + + if results.exists(): + results_by_check = results.values("check").annotate(count=Count("check")) + + results_with_checks = [] + for result in results_by_check: + results_with_checks.append( + (DATA_CHECKS_BY_NAME[result["check"]], result["count"]) + ) + + send_templated_mail( + template_name="data_checks", + from_email=get_site_preferences()["mail__address"], + recipient_list=[ + p.email + for p in get_site_preferences()["alsijil__data_checks_recipients"] + ], + context={"results": results_with_checks}, + ) + + results.update(sent=True) diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index b584083b4b0842610f4503a59cd15d72cac531ea..8f8b6fee054d8fdf136e51a61260bb9725421ec3 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -239,6 +239,7 @@ class DataCheckResult(ExtensibleModel): related_object = GenericForeignKey("content_type", "object_id") solved = models.BooleanField(default=False, verbose_name=_("Issue solved")) + sent = models.BooleanField(default=False, verbose_name=_("Notification sent")) @property def related_check(self) -> DataCheck: diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index bcefc075e2ac5025ebaf2b361abe3f2325b16563..01dedd9bbe6955226baf3c434f68da557b8557ee 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -1,8 +1,9 @@ from django.utils.translation import gettext as _ from dynamic_preferences.preferences import Section -from dynamic_preferences.types import BooleanPreference +from dynamic_preferences.types import BooleanPreference, ModelMultipleChoicePreference +from aleksis.core.models import Person from aleksis.core.registries import site_preferences_registry alsijil = Section("alsijil", verbose_name=_("Class register")) @@ -40,3 +41,24 @@ class AllowOpenPeriodsOnSameDay(BooleanPreference): help_text = _( "Lessons in the past are not affected by this setting, you can open them whenever you want." ) + + +@site_preferences_registry.register +class DataChecksSendEmails(BooleanPreference): + """Enable email sending if data checks detect problems.""" + + section = alsijil + name = "data_checks_send_emails" + default = False + verbose_name = _("Send emails if data checks detect problems") + + +@site_preferences_registry.register +class DataChecksEmailsRecipients(ModelMultipleChoicePreference): + """Email recipients for data check problem emails.""" + + section = alsijil + name = "data_checks_recipients" + default = [] + model = Person + verbose_name = _("Email recipients for data checks problem emails") diff --git a/aleksis/apps/alsijil/templates/templated_email/data_checks.css b/aleksis/apps/alsijil/templates/templated_email/data_checks.css new file mode 100644 index 0000000000000000000000000000000000000000..b385b185ecfe66dcca070e8eb024bbb091917f04 --- /dev/null +++ b/aleksis/apps/alsijil/templates/templated_email/data_checks.css @@ -0,0 +1,16 @@ +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/apps/alsijil/templates/templated_email/data_checks.email b/aleksis/apps/alsijil/templates/templated_email/data_checks.email new file mode 100644 index 0000000000000000000000000000000000000000..636dc27d36a37c650fc1a7cc7b8364653b00ce91 --- /dev/null +++ b/aleksis/apps/alsijil/templates/templated_email/data_checks.email @@ -0,0 +1,44 @@ +{% load i18n %} + +{% 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 in the digital class register. + 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 %} +{% endblock %} + +{% block html %} + <style> + {% include "templated_email/data_checks.css" %} + </style> + <p>{% trans "Hello," %}</p> + <p> + {% blocktrans %} + the system detected some new problems with your data in the digital class register. + Please take some time to inspect them and solve the issues or mark them as ignored. + {% endblocktrans %} + </p> + + <table> + <tr> + <th>{% trans "Problem description" %}</th> + <th>{% trans "Count of objects with new problems" %}</th> + </tr> + {% for result in results %} + <tr> + <td>{{ result.0.problem_name }}</td> + <td class="count">{{ result.1 }}</td> + </tr> + {% endfor %} + </table> +{% endblock %}