From 50fed7288854a4dc27b9db103f857df2ad8dfef8 Mon Sep 17 00:00:00 2001 From: Helge Jung <hej@c3pb.de> Date: Tue, 5 Nov 2024 09:09:24 +0100 Subject: [PATCH] core: make educated guesses for conf name in registration & password reset mail subjects --- src/backoffice/tests/auth.py | 2 +- src/core/locale/de/LC_MESSAGES/django.po | 8 ++- src/core/locale/en/LC_MESSAGES/django.po | 6 +++ .../core_password_reset_subject.txt | 2 +- .../registration/registration_subject.txt | 2 +- src/core/views/auth.py | 54 +++++++++++++++++-- src/plainui/tests/test_auth.py | 2 +- 7 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/backoffice/tests/auth.py b/src/backoffice/tests/auth.py index 7881dd107..e6db6cd5e 100644 --- a/src/backoffice/tests/auth.py +++ b/src/backoffice/tests/auth.py @@ -38,7 +38,7 @@ class RegistrationTests(BackOfficeTestCase): self.assertEqual(len(mail.outbox), 1) sent_mail = mail.outbox[0] self.assertEqual(sent_mail.to, ['unittest@example.com']) - self.assertEqual(sent_mail.subject, 'Activate your testserver account') + self.assertEqual(sent_mail.subject, f'Activate your {self.conf.name} account') reset_url_pattern = reverse('backoffice:signup_activate', kwargs={'uid_b64': 'uidb64', 'channel_id': 42, 'token': 'token-token'}) reset_url_pattern = re.escape(reset_url_pattern) reset_url_pattern = reset_url_pattern.replace('uidb64', '([^/]+)') diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po index 568e1721c..02de937a0 100644 --- a/src/core/locale/de/LC_MESSAGES/django.po +++ b/src/core/locale/de/LC_MESSAGES/django.po @@ -2525,6 +2525,12 @@ msgstr "Das Bild muss die Abmessungen von minimal %(min_width)spx/%(min_height)s msgid "Validation__error_image_square" msgstr "Das Bild muss quadratisch sein." +msgid "PasswordReset__email__sending__success" +msgstr "Sofern uns die angegebene E-Mail-Adresse bekannt ist, haben wir dir gerade eine E-Mail gesendet um dein Kennwort zurückzusetzen. Bitte klicke den darin enthaltenen Link!" + +msgid "PasswordReset__email__sending__failure" +msgstr "Leider gab es ein Problem beim Senden der Kennwort-Zurücksetz-E-Mail, bitte versuche es später erneut." + msgid "Registration" msgstr "Registrieren" @@ -2532,7 +2538,7 @@ msgid "Registration__email__sending__success" msgstr "Wir haben dir eine E-Mail gesendet um die angegebene Adresse zu bestätigen und das Konto zu aktivieren. Bitte klicke den darin enthaltenen Link!" msgid "Registration__email__sending__failure" -msgstr "Leider gab es ein Problem beim senden der Registrierungs-Email, bitte versuche es später erneut." +msgstr "Leider gab es ein Problem beim Senden der Registrierungs-E-Mail, bitte versuche es später erneut." msgid "registration__activation__user_does_not_exist" msgstr "Der zu aktivierende Account wurde nicht gefunden." diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po index a1d567be4..d448ab5d6 100644 --- a/src/core/locale/en/LC_MESSAGES/django.po +++ b/src/core/locale/en/LC_MESSAGES/django.po @@ -2507,6 +2507,12 @@ msgstr "The image must have the dimensions of minimal %(min_width)spx/%(min_heig msgid "Validation__error_image_square" msgstr "The image must be square" +msgid "PasswordReset__email__sending__success" +msgstr "If the address entered matches our records, we have just sent you an e-mail to reset your password. Please click the link in it!" + +msgid "PasswordReset__email__sending__failure" +msgstr "Unfortunately there was a problem sending the password reset email, please try again later." + msgid "Registration" msgstr "Sign Up" diff --git a/src/core/templates/registration/core_password_reset_subject.txt b/src/core/templates/registration/core_password_reset_subject.txt index e2aebe029..0709f18f5 100644 --- a/src/core/templates/registration/core_password_reset_subject.txt +++ b/src/core/templates/registration/core_password_reset_subject.txt @@ -1,2 +1,2 @@ {% load i18n %} -{% blocktranslate with site_name_safe=site_name|safe%}Password reset on {{ site_name_safe }}{% endblocktranslate %} +{% blocktranslate with site_name_safe=location|safe%}Password reset on {{ site_name_safe }}{% endblocktranslate %} diff --git a/src/core/templates/registration/registration_subject.txt b/src/core/templates/registration/registration_subject.txt index de30b71da..b74b89993 100644 --- a/src/core/templates/registration/registration_subject.txt +++ b/src/core/templates/registration/registration_subject.txt @@ -1,2 +1,2 @@ {% load i18n %} -{% blocktranslate with safe_site_name=site_name|safe %}Activate your {{ safe_site_name }} account{% endblocktranslate %} +{% blocktranslate with safe_site_name=location|safe %}Activate your {{ safe_site_name }} account{% endblocktranslate %} diff --git a/src/core/views/auth.py b/src/core/views/auth.py index a5ac41e65..82f8e0a7e 100644 --- a/src/core/views/auth.py +++ b/src/core/views/auth.py @@ -5,6 +5,7 @@ from typing import Any from django_ratelimit.decorators import ratelimit +from django.conf import settings from django.contrib import messages from django.contrib.auth.views import LoginView, PasswordResetConfirmView, PasswordResetView from django.core.exceptions import NON_FIELD_ERRORS, ImproperlyConfigured, ValidationError @@ -20,12 +21,36 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic import FormView, View from core.forms import LoginForm, PasswordResetForm, RegistrationForm -from core.models import PlatformUser, UserCommunicationChannel +from core.models import Conference, PlatformUser, UserCommunicationChannel from core.tokens import channel_activation_token logger = logging.getLogger(__name__) +class AuthLocationMixin: + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # try to guess the conference name from available context + # TODO: refactor with conference selection overhaul + conference_name = None + with contextlib.suppress(ImproperlyConfigured): + # try conference or conf properties + if (hasattr(self, 'conference') and (c := getattr(self, 'conference'))) or (hasattr(self, 'conf') and (c := getattr(self, 'conf'))): + conference_name = c.name + + # try configured public conference + else: + c = Conference.objects.filter(pk=settings.SELECTED_CONFERENCE_ID).first() + if c is not None: + conference_name = c.name + + # if all fails use the configured DJANGO_HOST + context['location'] = conference_name or settings.DJANGO_HOST + + return context + + @method_decorator(ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE, block=False), name='post') @method_decorator(ratelimit(key='post:username', rate='5/m', method=ratelimit.UNSAFE, block=False), name='post') class BaseLoginView(LoginView): @@ -46,7 +71,7 @@ class BaseLoginView(LoginView): return super().form_valid(form) -class BasePasswordResetView(PasswordResetView): +class BasePasswordResetView(AuthLocationMixin, PasswordResetView): email_template_name = 'registration/core_password_reset_email.html' extra_email_context = None form_class = PasswordResetForm @@ -59,6 +84,25 @@ class BasePasswordResetView(PasswordResetView): context.setdefault('retry', self.request.GET.get('retry', 'False') == 'True' if hasattr(self.request, 'GET') else False) return context + def form_valid(self, form): + opts = { + 'request': self.request, + 'email_template_name': self.email_template_name, + 'html_email_template_name': self.html_email_template_name, + 'subject_template_name': self.subject_template_name, + 'extra_email_context': (self.extra_email_context or {}) | self.get_context_data(), + } + try: + form.save(**opts) + logger.info('Sending password reset mail to "%(email)s".', {'email': form.cleaned_data['email']}) + messages.success(self.request, _('PasswordReset__email__sending__success')) + except SMTPException: + logger.error('Failed to send password reset mail to "%(email)s".', {'email': form.cleaned_data['email']}) + messages.error(self.request, _('PasswordReset__email__sending__failure')) + form.errors[NON_FIELD_ERRORS] = ValidationError(_('PasswordReset__email__sending__failure')) + return self.form_invalid(form) + return super().form_valid(form) + class BasePasswordResetConfirmView(PasswordResetConfirmView): @method_decorator(sensitive_post_parameters()) @@ -72,7 +116,7 @@ class BasePasswordResetConfirmView(PasswordResetConfirmView): @method_decorator(ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE, block=False), name='post') @method_decorator(ratelimit(key='post:username', rate='5/m', method=ratelimit.UNSAFE, block=False), name='post') -class BaseRegistrationView(FormView): +class BaseRegistrationView(AuthLocationMixin, FormView): email_template_name = 'registration/registration_email.html' extra_email_context = None form_class = RegistrationForm @@ -100,14 +144,14 @@ class BaseRegistrationView(FormView): 'subject_template_name': self.subject_template_name, 'request': self.request, 'html_email_template_name': self.html_email_template_name, - 'extra_email_context': self.extra_email_context, + 'extra_email_context': (self.extra_email_context or {}) | self.get_context_data(), } try: form.save(**opts) logger.info('Sent account activation mail to "%(email)s" for %(user)s.', {'email': form.instance.email, 'user': form.instance.username}) messages.success(self.request, _('Registration__email__sending__success')) except SMTPException: - logger.info('Failed to send account activation mail to "%(email)s" for %(user)s.', {'email': form.instance.email, 'user': form.instance.username}) + logger.error('Failed to send account activation mail to "%(email)s" for %(user)s.', {'email': form.instance.email, 'user': form.instance.username}) messages.error(self.request, _('Registration__email__sending__failure')) form.errors[NON_FIELD_ERRORS] = ValidationError(_('Registration__email__sending__failure')) return self.form_invalid(form) diff --git a/src/plainui/tests/test_auth.py b/src/plainui/tests/test_auth.py index ec55b78c3..7dc7cc95a 100644 --- a/src/plainui/tests/test_auth.py +++ b/src/plainui/tests/test_auth.py @@ -80,7 +80,7 @@ class AuthViewTest(ViewsTestBase): self.assertEqual(len(mail.outbox), 1) sent_mail = mail.outbox[0] self.assertEqual(sent_mail.to, ['no@where.test']) - self.assertEqual(sent_mail.subject, 'Password reset on testserver') + self.assertEqual(sent_mail.subject, f'Password reset on {self.conf.name}') reset_url_pattern = reverse('plainui:password_reset_confirm', kwargs={'uidb64': 'uidb64', 'token': 'token'}) reset_url_pattern = re.escape(reset_url_pattern) reset_url_pattern = reset_url_pattern.replace('uidb64', '([^/]+)') -- GitLab