Skip to content

Commit 897d1a7

Browse files
committed
feat(email templates): added editable emails and templates
1 parent 14a4ff2 commit 897d1a7

File tree

7 files changed

+172
-3
lines changed

7 files changed

+172
-3
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import TypedDict
2+
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from symfexit.emails._templates.base import BodyTemplate
6+
7+
8+
class ApplyContext(TypedDict):
9+
firstname: str
10+
11+
12+
class ApplyEmail(BodyTemplate[ApplyContext]):
13+
code = "apply"
14+
label = _("Apply template")
15+
16+
subject_template = "Applied"
17+
html_template = "You succesfully applied"
18+
text_template = "You succesfully applied"
19+
20+
@classmethod
21+
def get_input_context(cls):
22+
return [
23+
*super().get_input_context(),
24+
{"firstname": _("First name of the member")},
25+
]
26+
27+
pass
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from typing import TypedDict
2+
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from symfexit.emails._templates.base import BodyTemplate
6+
7+
8+
class ApplyContext(TypedDict):
9+
firstname: str
10+
email: str
11+
url: str
12+
13+
14+
class PasswordResetEmail(BodyTemplate[ApplyContext]):
15+
code = "password-reset"
16+
label = _("Request new password template")
17+
18+
subject_template = "Password reset on {{site_url}}"
19+
html_template = """<p>You're receiving this email because you requested a password reset for your user account at {{ site_url }}.</p>
20+
21+
<p>Please go to the following page and choose a new password:</p>
22+
23+
<p><a href="{{url}}">{{url}}</a></p>
24+
25+
<p>In case you’ve forgotten, you are: {{email}}</p>
26+
27+
<p>Thanks for using our site!</p>
28+
29+
{{site_title}}"""
30+
text_template = """You're receiving this email because you requested a password reset for your user account at {{ site_url }}.
31+
32+
Please go to the following page and choose a new password:
33+
34+
{{url}}
35+
36+
In case you’ve forgotten, you are: {{email}}
37+
38+
Thanks for using our site!
39+
40+
{{site_title}}"""
41+
42+
@classmethod
43+
def get_input_context(cls):
44+
return [
45+
*super().get_input_context(),
46+
("firstname", _("First name of the member")),
47+
("email", _("Email address of the member")),
48+
("url", _("Url of the reset password link"), True),
49+
]

symfexit/emails/_templates/manager.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from typing import TYPE_CHECKING
44

55
from symfexit.emails._templates.base import BaseEmailComponent, WrapperLayout
6+
from symfexit.emails._templates.emails.apply import ApplyEmail
7+
from symfexit.emails._templates.emails.password_request import PasswordResetEmail
8+
from symfexit.emails._templates.layouts.base import BaseLayout
69

710
if TYPE_CHECKING:
811
from .base import BodyTemplate
@@ -24,8 +27,8 @@ def find(cls, code: str) -> type[BodyTemplate] | None:
2427

2528

2629
class EmailTemplateManager(BaseManager):
27-
_registry: list[type[BodyTemplate]] = []
30+
_registry: list[type[BodyTemplate]] = [ApplyEmail, PasswordResetEmail]
2831

2932

3033
class EmailLayoutManager(BaseManager):
31-
_registry: list[type[WrapperLayout]] = []
34+
_registry: list[type[WrapperLayout]] = [BaseLayout]

symfexit/emails/locale/nl/LC_MESSAGES/django.po

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2025-11-15 15:33+0100\n"
11+
"POT-Creation-Date: 2025-11-15 15:35+0100\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -36,6 +36,29 @@ msgstr "Logo van de organisatie"
3636
msgid "Email template outlet"
3737
msgstr "E-mail sjablonen"
3838

39+
#: symfexit/emails/_templates/emails/apply.py:14
40+
msgid "Apply template"
41+
msgstr "Aanmelden sjabloon"
42+
43+
#: symfexit/emails/_templates/emails/apply.py:24
44+
#: symfexit/emails/_templates/emails/password_request.py:46
45+
msgid "First name of the member"
46+
msgstr "Voornaam van het lid"
47+
48+
#: symfexit/emails/_templates/emails/password_request.py:16
49+
msgid "Request new password template"
50+
msgstr "Nieuw wachtwoord aanvragen sjabloon"
51+
52+
#: symfexit/emails/_templates/emails/password_request.py:47
53+
#, fuzzy
54+
#| msgid "Full name of the member"
55+
msgid "Email address of the member"
56+
msgstr "Volledige naam van het lid"
57+
58+
#: symfexit/emails/_templates/emails/password_request.py:48
59+
msgid "Url of the reset password link"
60+
msgstr "URL van de link voor het opnieuw instellen van het wachtwoord"
61+
3962
#: symfexit/emails/_templates/layouts/base.py:7
4063
#, fuzzy
4164
#| msgid "email layout"

symfexit/root/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
# from symfexit.adminsite.admin import admin_site
2424
from symfexit.root.utils import enable_if
25+
from symfexit.root.views import CustomPasswordResetView
2526

2627
try:
2728
import django_browser_reload # noqa
@@ -80,3 +81,8 @@ def chrome_devtools(request):
8081
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
8182
)
8283
)
84+
85+
# Prepend the custom view to override the default password_reset URL
86+
urlpatterns = [
87+
path("accounts/password_reset/", CustomPasswordResetView.as_view(), name="password_reset"),
88+
] + list(urlpatterns) # keep the rest of the original list intact

symfexit/root/views.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# symfexit/root/views.py
2+
from django.contrib.auth.forms import PasswordResetForm
3+
from django.contrib.auth.tokens import default_token_generator
4+
from django.contrib.auth.views import PasswordResetView
5+
from django.urls import reverse
6+
from django.utils.encoding import force_bytes
7+
from django.utils.http import urlsafe_base64_encode
8+
9+
from symfexit.emails._templates.emails.password_request import PasswordResetEmail
10+
from symfexit.emails._templates.render import send_email
11+
from symfexit.members.admin import Member
12+
13+
14+
class MyPasswordResetForm(PasswordResetForm):
15+
def send_mail(
16+
self,
17+
subject_template_name,
18+
email_template_name,
19+
context,
20+
from_email,
21+
to_email,
22+
html_email_template_name=None,
23+
):
24+
# context = {
25+
# "email": user_email,
26+
# "domain": domain,
27+
# "site_name": site_name,
28+
# "uid": urlsafe_base64_encode(user_pk_bytes),
29+
# "user": user,
30+
# "token": token_generator.make_token(user),
31+
# "protocol": "https" if use_https else "http",
32+
# **(extra_email_context or {}),
33+
# }
34+
user: Member = context["user"]
35+
reset_path = reverse(
36+
"password_reset_confirm", # your URL‑conf name
37+
kwargs={
38+
"uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
39+
"token": default_token_generator.make_token(user),
40+
},
41+
)
42+
43+
# # Prepend scheme+host to make a full absolute URL
44+
reset_url = f"{context['protocol']}://{context['domain']}{reset_path}"
45+
send_email(
46+
PasswordResetEmail(
47+
{
48+
"firstname": user.first_name,
49+
"url": reset_url,
50+
"email": user.email,
51+
}
52+
),
53+
recipient_list=[to_email],
54+
)
55+
56+
57+
class CustomPasswordResetView(PasswordResetView):
58+
form_class = MyPasswordResetForm

symfexit/signup/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from django.urls import reverse, reverse_lazy
77
from django.views.generic import FormView
88

9+
from symfexit.emails._templates.emails.apply import ApplyEmail
10+
from symfexit.emails._templates.render import send_email
911
from symfexit.payments.models import Order
1012
from symfexit.payments.registry import payments_registry
1113
from symfexit.signup.forms import SignupForm
@@ -22,6 +24,7 @@ class MemberSignup(FormView):
2224
def form_valid(self, form):
2325
logout(self.request)
2426
application = form.save()
27+
send_email(ApplyEmail({"firstname": application.first_name}), application.email)
2528
return HttpResponseRedirect(reverse("signup:payment", args=[application.eid]))
2629

2730

0 commit comments

Comments
 (0)