From d8a20175b685e26df3fc40f338a2cf074b26ccf6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 21 Mar 2021 11:43:14 -0700 Subject: [PATCH 1/5] Adds email template layout and formatting --- bookwyrm/emailing.py | 32 ++++++++++++------ bookwyrm/static/images/logo-small.png | Bin 7039 -> 7314 bytes bookwyrm/templates/email/html_layout.html | 26 ++++++++++++++ .../templates/email/invite/html_content.html | 17 +++++++++- bookwyrm/templates/email/invite/subject.html | 2 +- .../templates/email/invite/text_content.html | 10 +++++- .../email/password_reset/html_content.html | 15 +++++++- .../email/password_reset/subject.html | 2 +- .../email/password_reset/text_content.html | 9 ++++- bookwyrm/templates/email/preview.html | 19 +++++++++++ bookwyrm/templates/email/snippets/action.html | 5 +++ bookwyrm/templates/email/text_layout.html | 3 ++ .../templates/password_reset_request.html | 7 +++- bookwyrm/urls.py | 9 +++-- bookwyrm/views/site.py | 16 ++++++++- 15 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 bookwyrm/templates/email/html_layout.html create mode 100644 bookwyrm/templates/email/preview.html create mode 100644 bookwyrm/templates/email/snippets/action.html create mode 100644 bookwyrm/templates/email/text_layout.html diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index 7069286d7..4025aa220 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -4,25 +4,37 @@ from django.template.loader import get_template from bookwyrm import models from bookwyrm.tasks import app +from bookwyrm.settings import DOMAIN + + +def email_data(): + """ fields every email needs """ + site = models.SiteSettings.objects.get() + if site.logo_small: + logo_path = '/images/{}'.format(site.logo_small.url) + else: + logo_path = '/static/images/logo-small.png' + + return { + "site_name": site.name, + "logo": logo_path, + "domain": DOMAIN, + "user": None, + } def invite_email(invite_request): """ send out an invite code """ - site = models.SiteSettings.objects.get() - data = { - "site_name": site.name, - "invite_link": invite_request.invite.link, - } + data = email_data() + data["invite_link"] = invite_request.invite.link send_email.delay(invite_request.email, "invite", data) def password_reset_email(reset_code): """ generate a password reset email """ - site = models.SiteSettings.objects.get() - data = { - "site_name": site.name, - "reset_link": reset_code.link, - } + data = email_data() + data["reset_link"] = reset_code.link + data["user"] = reset_code.user.diplay_name send_email.delay(reset_code.user.email, "password_reset", data) diff --git a/bookwyrm/static/images/logo-small.png b/bookwyrm/static/images/logo-small.png index 10ea7a38fce9c962399af6697619cc9952cde89a..72f49ef78b35ce0ab8408ea3a254398274a219bf 100644 GIT binary patch delta 6585 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 VcUgeQaRvqk22WQ%mvv4FO#rG=@3Q~^ diff --git a/bookwyrm/templates/email/html_layout.html b/bookwyrm/templates/email/html_layout.html new file mode 100644 index 000000000..02527ff52 --- /dev/null +++ b/bookwyrm/templates/email/html_layout.html @@ -0,0 +1,26 @@ +{% load i18n %} +
+ + +
+

+ {% if user %}{{ user }},{% else %}{% trans "Hi there," %}{% endif %} +

+ {% block content %}{% endblock %} +
+ +
+

{% blocktrans %}BookWyrm hosted on {{ site_name }}{% endblocktrans %}

+ {% if user %} +

{% trans "Email preference" %}

+ {% endif %} +
+
diff --git a/bookwyrm/templates/email/invite/html_content.html b/bookwyrm/templates/email/invite/html_content.html index 672d34077..358e23dc1 100644 --- a/bookwyrm/templates/email/invite/html_content.html +++ b/bookwyrm/templates/email/invite/html_content.html @@ -1,2 +1,17 @@ +{% extends 'email/html_layout.html' %} {% load i18n %} -{% blocktrans %}Join {{ site_name }}{% endblocktrans %} + +{% block content %} +

+ {% blocktrans %}You're invited to join {{ site_name }}!{% endblocktrans %} +

+ +{% trans "Join Now" as text %} +{% include 'email/snippets/action.html' with path=invite_link text=text %} + +

+ {% url 'code-of-conduct' as coc_path %} + {% url 'about' as about_path %} + {% blocktrans %}Learn more about this instance.{% endblocktrans %} +

+{% endblock %} diff --git a/bookwyrm/templates/email/invite/subject.html b/bookwyrm/templates/email/invite/subject.html index b3c4a141d..efb8be5ae 100644 --- a/bookwyrm/templates/email/invite/subject.html +++ b/bookwyrm/templates/email/invite/subject.html @@ -1,2 +1,2 @@ {% load i18n %} -{% blocktrans %}You're invited! Join {{ site_name }}{% endblocktrans %} +{% blocktrans %}You're invited to join {{ site_name }}!{% endblocktrans %} diff --git a/bookwyrm/templates/email/invite/text_content.html b/bookwyrm/templates/email/invite/text_content.html index 7add9010f..c3fcdc04e 100644 --- a/bookwyrm/templates/email/invite/text_content.html +++ b/bookwyrm/templates/email/invite/text_content.html @@ -1,2 +1,10 @@ +{% extends 'email/text_layout.html' %} {% load i18n %} -{% blocktrans %}Join {{ site_name }}: {{ invite_link }}{% endblocktrans %} +{% block content %} +{% blocktrans %}You're invited to join {{ site_name }}! Click the link below to create an account.{% endblocktrans %} + +{{ invite_link }} + +{% trans "Learn more about this instance:" %} https://{{ domain }}{% url 'about' %} + +{% endblock %} diff --git a/bookwyrm/templates/email/password_reset/html_content.html b/bookwyrm/templates/email/password_reset/html_content.html index e3a006b04..eef0e5e59 100644 --- a/bookwyrm/templates/email/password_reset/html_content.html +++ b/bookwyrm/templates/email/password_reset/html_content.html @@ -1,2 +1,15 @@ +{% extends 'email/html_layout.html' %} {% load i18n %} -{% blocktrans %}Your password reset link is: {{ reset_link }}{% endblocktrans %} + +{% block content %} +

+ {% blocktrans %}You requested to reset your {{ site_name }} password. Click the link below to set a new password and log in to your account.{% endblocktrans %} +

+ +{% trans "Reset Password" as text %} +{% include 'email/snippets/action.html' with path=reset_link text=text %} + +

+ {% trans "If you didn't request to reset your password, you can ignore this email." %} +

+{% endblock %} diff --git a/bookwyrm/templates/email/password_reset/subject.html b/bookwyrm/templates/email/password_reset/subject.html index b216a6c26..886801402 100644 --- a/bookwyrm/templates/email/password_reset/subject.html +++ b/bookwyrm/templates/email/password_reset/subject.html @@ -1,2 +1,2 @@ {% load i18n %} -{% blocktrans %}Reset your password on {{ site_name }}{% endblocktrans %} +{% blocktrans %}Reset your {{ site_name }} password{% endblocktrans %} diff --git a/bookwyrm/templates/email/password_reset/text_content.html b/bookwyrm/templates/email/password_reset/text_content.html index e3a006b04..b5cf754ef 100644 --- a/bookwyrm/templates/email/password_reset/text_content.html +++ b/bookwyrm/templates/email/password_reset/text_content.html @@ -1,2 +1,9 @@ +{% extends 'email/text_layout.html' %} {% load i18n %} -{% blocktrans %}Your password reset link is: {{ reset_link }}{% endblocktrans %} +{% block content %} +{% blocktrans %}You requested to reset your {{ site_name }} password. Click the link below to set a new password and log in to your account.{% endblocktrans %} + +{{ reset_link }} + +{% trans "If you didn't request to reset your password, you can ignore this email." %} +{% endblock %} diff --git a/bookwyrm/templates/email/preview.html b/bookwyrm/templates/email/preview.html new file mode 100644 index 000000000..66d856c08 --- /dev/null +++ b/bookwyrm/templates/email/preview.html @@ -0,0 +1,19 @@ + + +
+ Subject: {% include subject_path %} +
+
+ + Html email: +
+ {% include html_content_path %} +
+
+ + Text email: +
+ {% include text_content_path %} +
+ + diff --git a/bookwyrm/templates/email/snippets/action.html b/bookwyrm/templates/email/snippets/action.html new file mode 100644 index 000000000..56feb9efd --- /dev/null +++ b/bookwyrm/templates/email/snippets/action.html @@ -0,0 +1,5 @@ +

+ + {{ text }} + +

diff --git a/bookwyrm/templates/email/text_layout.html b/bookwyrm/templates/email/text_layout.html new file mode 100644 index 000000000..cd0444f16 --- /dev/null +++ b/bookwyrm/templates/email/text_layout.html @@ -0,0 +1,3 @@ +{% load i18n %} +{% if user %}{{ user.display_name }},{% else %}{% trans "Hi there," %}{% endif %} +{% block content %}{% endblock %} diff --git a/bookwyrm/templates/password_reset_request.html b/bookwyrm/templates/password_reset_request.html index 97191afa4..5d877442f 100644 --- a/bookwyrm/templates/password_reset_request.html +++ b/bookwyrm/templates/password_reset_request.html @@ -8,7 +8,9 @@

{% trans "Reset Password" %}

- {% if message %}

{{ message }}

{% endif %} + + {% if message %}

{{ message }}

{% endif %} +

{% trans "A link to reset your password will be sent to your email address" %}

{% csrf_token %} @@ -16,6 +18,9 @@
+ {% if error %} +

{{ error }}

+ {% endif %}
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 30c1bba4e..4aced6fe6 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -48,6 +48,11 @@ urlpatterns = [ ), # admin re_path(r"^settings/site-settings", views.Site.as_view(), name="settings-site"), + re_path( + r"^settings/email-preview", + views.site.email_preview, + name="settings-email-preview", + ), re_path( r"^settings/federation", views.Federation.as_view(), name="settings-federation" ), @@ -87,8 +92,8 @@ urlpatterns = [ ), re_path(r"^report/?$", views.make_report, name="report"), # landing pages - re_path(r"^about/?$", views.About.as_view()), - path("", views.Home.as_view()), + re_path(r"^about/?$", views.About.as_view(), name="about"), + path("", views.Home.as_view(), name="landing"), re_path(r"^discover/?$", views.Discover.as_view()), re_path(r"^notifications/?$", views.Notifications.as_view()), # feeds diff --git a/bookwyrm/views/site.py b/bookwyrm/views/site.py index ce64e6e0f..5b33b92a7 100644 --- a/bookwyrm/views/site.py +++ b/bookwyrm/views/site.py @@ -5,7 +5,7 @@ from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import forms, models +from bookwyrm import emailing, forms, models # pylint: disable= no-self-use @@ -33,3 +33,17 @@ class Site(View): form.save() return redirect("settings-site") + + +@login_required +@permission_required("bookwyrm.edit_instance_settings", raise_exception=True) +def email_preview(request): + """ for development, renders and example email template """ + template = request.GET.get('email') + data = emailing.email_data() + data["subject_path"] = "email/{}/subject.html".format(template) + data["html_content_path"] = "email/{}/html_content.html".format(template) + data["text_content_path"] = "email/{}/text_content.html".format(template) + data["reset_link"] = "https://example.com/link" + data["invite_link"] = "https://example.com/link" + return TemplateResponse(request, "email/preview.html", data) From c76ad0a3122f481e7aedfb5f5759868598c73d61 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 21 Mar 2021 12:06:20 -0700 Subject: [PATCH 2/5] Celery can't render tempaltes --- bookwyrm/emailing.py | 23 +++++++++++++---------- bookwyrm/views/password.py | 6 ++++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index 4025aa220..e58423e82 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -27,34 +27,37 @@ def invite_email(invite_request): """ send out an invite code """ data = email_data() data["invite_link"] = invite_request.invite.link - send_email.delay(invite_request.email, "invite", data) + send_email.delay(invite_request.email, *format_email("invite", data)) def password_reset_email(reset_code): """ generate a password reset email """ data = email_data() data["reset_link"] = reset_code.link - data["user"] = reset_code.user.diplay_name - send_email.delay(reset_code.user.email, "password_reset", data) + data["user"] = reset_code.user.display_name + send_email.delay(reset_code.user.email, *format_email("password_reset", data)) - -@app.task -def send_email(recipient, message_name, data): - """ use a task to send the email """ +def format_email(email_name, data): + """ render the email templates """ subject = ( - get_template("email/{}/subject.html".format(message_name)).render(data).strip() + get_template("email/{}/subject.html".format(email_name)).render(data).strip() ) html_content = ( - get_template("email/{}/html_content.html".format(message_name)) + get_template("email/{}/html_content.html".format(email_name)) .render(data) .strip() ) text_content = ( - get_template("email/{}/text_content.html".format(message_name)) + get_template("email/{}/text_content.html".format(email_name)) .render(data) .strip() ) + return (subject, html_content, text_content) + +@app.task +def send_email(recipient, subject, html_content, text_content): + """ use a task to send the email """ email = EmailMultiAlternatives(subject, text_content, None, [recipient]) email.attach_alternative(html_content, "text/html") email.send() diff --git a/bookwyrm/views/password.py b/bookwyrm/views/password.py index e853d16bf..2926b9d76 100644 --- a/bookwyrm/views/password.py +++ b/bookwyrm/views/password.py @@ -5,6 +5,7 @@ from django.core.exceptions import PermissionDenied from django.shortcuts import redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ from django.views import View from bookwyrm import models @@ -28,7 +29,8 @@ class PasswordResetRequest(View): try: user = models.User.objects.get(email=email) except models.User.DoesNotExist: - return redirect("/password-reset") + data = {"error": _("No user with that email address was found.")} + return TemplateResponse(request, "password_reset_request.html", data) # remove any existing password reset cods for this user models.PasswordReset.objects.filter(user=user).all().delete() @@ -36,7 +38,7 @@ class PasswordResetRequest(View): # create a new reset code code = models.PasswordReset.objects.create(user=user) password_reset_email(code) - data = {"message": "Password reset link sent to %s" % email} + data = {"message": _("A password reset link sent to %s" % email)} return TemplateResponse(request, "password_reset_request.html", data) From da2d146f0bccc99c10e8b02606cf1c1503a619fd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 21 Mar 2021 12:07:58 -0700 Subject: [PATCH 3/5] runs black for python formatting --- bookwyrm/emailing.py | 5 +++-- bookwyrm/views/site.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index e58423e82..47ac59df5 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -11,9 +11,9 @@ def email_data(): """ fields every email needs """ site = models.SiteSettings.objects.get() if site.logo_small: - logo_path = '/images/{}'.format(site.logo_small.url) + logo_path = "/images/{}".format(site.logo_small.url) else: - logo_path = '/static/images/logo-small.png' + logo_path = "/static/images/logo-small.png" return { "site_name": site.name, @@ -37,6 +37,7 @@ def password_reset_email(reset_code): data["user"] = reset_code.user.display_name send_email.delay(reset_code.user.email, *format_email("password_reset", data)) + def format_email(email_name, data): """ render the email templates """ subject = ( diff --git a/bookwyrm/views/site.py b/bookwyrm/views/site.py index 5b33b92a7..e58976607 100644 --- a/bookwyrm/views/site.py +++ b/bookwyrm/views/site.py @@ -39,7 +39,7 @@ class Site(View): @permission_required("bookwyrm.edit_instance_settings", raise_exception=True) def email_preview(request): """ for development, renders and example email template """ - template = request.GET.get('email') + template = request.GET.get("email") data = emailing.email_data() data["subject_path"] = "email/{}/subject.html".format(template) data["html_content_path"] = "email/{}/html_content.html".format(template) From a0b106f6bbfadabb1d8d50e060d5e14b53550d48 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 21 Mar 2021 12:30:53 -0700 Subject: [PATCH 4/5] Adds emailing tests --- bookwyrm/tests/test_emailing.py | 51 +++++++++++++++++++++++++ bookwyrm/tests/test_goodreads_import.py | 3 +- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 bookwyrm/tests/test_emailing.py diff --git a/bookwyrm/tests/test_emailing.py b/bookwyrm/tests/test_emailing.py new file mode 100644 index 000000000..5d7d4894b --- /dev/null +++ b/bookwyrm/tests/test_emailing.py @@ -0,0 +1,51 @@ +""" test creating emails """ +from unittest.mock import patch + +from django.test import TestCase +from django.test.client import RequestFactory +import responses + +from bookwyrm import emailing, models + + +@patch("bookwyrm.emailing.send_email.delay") +class Emailing(TestCase): + """ every response to a get request, html or json """ + + def setUp(self): + """ we need basic test data and mocks """ + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + models.SiteSettings.objects.create() + + def test_invite_email(self, email_mock): + """ load the invite email """ + invite_request = models.InviteRequest.objects.create( + email="test@email.com", + invite=models.SiteInvite.objects.create(user=self.local_user), + ) + + emailing.invite_email(invite_request) + + self.assertEqual(email_mock.call_count, 1) + args = email_mock.call_args[0] + self.assertEqual(args[0], "test@email.com") + self.assertEqual(args[1], "You're invited to join BookWyrm!") + self.assertEqual(len(args), 4) + + def test_password_reset_email(self, email_mock): + """ load the password reset email """ + reset = models.PasswordReset.objects.create(user=self.local_user) + emailing.password_reset_email(reset) + + self.assertEqual(email_mock.call_count, 1) + args = email_mock.call_args[0] + self.assertEqual(args[0], "mouse@mouse.mouse") + self.assertEqual(args[1], "Reset your BookWyrm password") + self.assertEqual(len(args), 4) diff --git a/bookwyrm/tests/test_goodreads_import.py b/bookwyrm/tests/test_goodreads_import.py index 080ccd15b..a62cfdd28 100644 --- a/bookwyrm/tests/test_goodreads_import.py +++ b/bookwyrm/tests/test_goodreads_import.py @@ -9,7 +9,6 @@ import responses from bookwyrm import models, importer from bookwyrm.goodreads_import import GoodreadsImporter -from bookwyrm import importer from bookwyrm.settings import DOMAIN @@ -17,8 +16,8 @@ class GoodreadsImport(TestCase): """ importing from goodreads csv """ def setUp(self): - self.importer = GoodreadsImporter() """ use a test csv """ + self.importer = GoodreadsImporter() datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") self.csv = open(datafile, "r", encoding=self.importer.encoding) self.user = models.User.objects.create_user( From b29be11862586d97eb76bf72c469a628dba8d5d6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 21 Mar 2021 12:33:19 -0700 Subject: [PATCH 5/5] Updates reset password view test --- bookwyrm/tests/views/test_password.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index f67f5538f..53a9bcbdc 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -42,7 +42,8 @@ class PasswordViews(TestCase): request = self.factory.post("", {"email": "aa@bb.ccc"}) view = views.PasswordResetRequest.as_view() resp = view(request) - self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.status_code, 200) + resp.render() request = self.factory.post("", {"email": "mouse@mouse.com"}) with patch("bookwyrm.emailing.send_email.delay"):