chore(mailer/*): reformat using ruff (#14)

mk-personal-profile
Christian Merten 4 weeks ago committed by GitHub
parent aae75ce291
commit c072f5caf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,20 +1,29 @@
from django.contrib import admin, messages
import json
from contrib.admin import CommonAdminInlineMixin
from contrib.admin import CommonAdminMixin
from django.conf import settings
from django.contrib import admin
from django.contrib import messages
from django.contrib.admin import helpers
from django.utils.translation import gettext_lazy as _
from django.shortcuts import render
from django.db import models
from django import forms
#from easy_select2 import apply_select2
import json
from rules.contrib.admin import ObjectPermissionsModelAdmin
from .models import Message, Attachment, MessageForm, EmailAddress, EmailAddressForm
from .mailutils import NOT_SENT, PARTLY_SENT
from members.models import Member
from django.utils.translation import (
gettext_lazy as _,
)
from members.admin import FilteredMemberFieldMixin
from contrib.admin import CommonAdminMixin, CommonAdminInlineMixin
from members.models import Member
from rules.contrib.admin import (
ObjectPermissionsModelAdmin,
)
from .mailutils import NOT_SENT
from .mailutils import PARTLY_SENT
from .models import Attachment
from .models import EmailAddress
from .models import EmailAddressForm
from .models import Message
from .models import MessageForm
# from easy_select2 import apply_select2
class AttachmentInline(CommonAdminInlineMixin, admin.TabularInline):
@ -23,91 +32,127 @@ class AttachmentInline(CommonAdminInlineMixin, admin.TabularInline):
class EmailAddressAdmin(FilteredMemberFieldMixin, admin.ModelAdmin):
list_display = ('email', 'internal_only')
fields = ('name', 'to_members', 'to_groups', 'internal_only')
#formfield_overrides = {
list_display = ("email", "internal_only")
fields = (
"name",
"to_members",
"to_groups",
"internal_only",
)
# formfield_overrides = {
# models.ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# models.ForeignKey: {'widget': apply_select2(forms.Select)}
#}
filter_horizontal = ('to_members',)
# }
filter_horizontal = ("to_members",)
form = EmailAddressForm
class MessageAdmin(FilteredMemberFieldMixin, CommonAdminMixin, ObjectPermissionsModelAdmin):
class MessageAdmin(
FilteredMemberFieldMixin,
CommonAdminMixin,
ObjectPermissionsModelAdmin,
):
"""Message creation view"""
exclude = ('created_by', 'to_notelist')
list_display = ('subject', 'get_recipients', 'sent')
search_fields = ('subject',)
list_filter = ('sent',)
exclude = ("created_by", "to_notelist")
list_display = (
"subject",
"get_recipients",
"sent",
)
search_fields = ("subject",)
list_filter = ("sent",)
change_form_template = "mailer/change_form.html"
readonly_fields = ('sent',)
#formfield_overrides = {
readonly_fields = ("sent",)
# formfield_overrides = {
# models.ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# models.ForeignKey: {'widget': apply_select2(forms.Select)}
#}
# }
inlines = [AttachmentInline]
actions = ['send_message']
actions = ["send_message"]
form = MessageForm
filter_horizontal = ('to_members','reply_to')
filter_horizontal = ("to_members", "reply_to")
def save_model(self, request, obj, form, change):
if not change and hasattr(request.user, 'member'):
if not change and hasattr(request.user, "member"):
obj.created_by = request.user.member
super().save_model(request, obj, form, change)
def send_message(self, request, queryset):
if request.POST.get('confirmed'):
if request.POST.get("confirmed"):
for msg in queryset:
submit_message(msg, request)
else:
context = {
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'mails': queryset,
'ids': queryset.values_list("id"),
'some_sent': any(m.sent for m in queryset)}
return render(request, 'mailer/confirm_send.html', context)
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
"mails": queryset,
"ids": queryset.values_list("id"),
"some_sent": any(m.sent for m in queryset),
}
return render(
request,
"mailer/confirm_send.html",
context,
)
send_message.short_description = _("Send message")
def response_change(self, request, obj):
if "_send" in request.POST:
submit_message(obj, request)
return super(MessageAdmin, self).response_change(request, obj)
return super().response_change(request, obj)
def response_add(self, request, obj):
if "_send" in request.POST:
submit_message(obj, request)
return super(MessageAdmin, self).response_add(request, obj)
return super().response_add(request, obj)
def get_form(self, request, obj=None, **kwargs):
form = super(MessageAdmin, self).get_form(request, obj, **kwargs)
raw_members = request.GET.get('members', None)
form = super().get_form(request, obj, **kwargs)
raw_members = request.GET.get("members", None)
if raw_members is not None:
m_ids = json.loads(raw_members)
if type(m_ids) != list:
if type(m_ids) is not list:
return form
members = Member.objects.filter(pk__in=m_ids)
form.base_fields['to_members'].initial = members
form.base_fields["to_members"].initial = members
return form
def submit_message(msg, request):
sender = None
if not hasattr(request.user, 'member'):
messages.error(request, _("Your account is not connected to a member. Please contact your system administrator."))
if not hasattr(request.user, "member"):
messages.error(
request,
_(
"Your account is not connected to a member. Please contact your system administrator."
),
)
return
sender = request.user.member
if not sender.has_internal_email():
messages.error(request,
_("Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s.") % {'domains': ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)})
messages.error(
request,
_(
"Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s."
)
% {"domains": ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)},
)
return
success = msg.submit(sender)
if success == NOT_SENT:
messages.error(request, _("Failed to send message"))
elif success == PARTLY_SENT:
messages.warning(request, _("Failed to send some messages"))
messages.warning(
request,
_("Failed to send some messages"),
)
else:
messages.success(request, _("Successfully sent message"))
messages.success(
request,
_("Successfully sent message"),
)
admin.site.register(Message, MessageAdmin)

@ -3,5 +3,5 @@ from django.utils.translation import gettext_lazy as _
class MailerConfig(AppConfig):
name = 'mailer'
verbose_name = _('mailer')
name = "mailer"
verbose_name = _("mailer")

@ -1,8 +1,8 @@
import logging
from django.conf import settings
from django.core import mail
from django.core.mail import EmailMessage
from django.conf import settings
import logging
import os
logger = logging.getLogger(__name__)
@ -10,14 +10,16 @@ logger = logging.getLogger(__name__)
NOT_SENT, SENT, PARTLY_SENT = 0, 1, 2
def send(subject, content, sender, recipients, message_id=None, reply_to=None,
attachments=None, cc=None):
def send(
subject, content, sender, recipients, message_id=None, reply_to=None, attachments=None, cc=None
):
failed, succeeded = False, False
if type(recipients) != list:
if type(recipients) is not list:
recipients = [recipients]
if not cc:
cc = []
elif type(cc) != list:
elif type(cc) is not list:
cc = [cc]
if reply_to is not None:
kwargs = {"reply_to": reply_to}
@ -26,15 +28,16 @@ def send(subject, content, sender, recipients, message_id=None, reply_to=None,
if sender == settings.DEFAULT_SENDING_MAIL:
sender = addr_with_name(settings.DEFAULT_SENDING_MAIL, settings.DEFAULT_SENDING_NAME)
url = prepend_base_url("/newsletter/unsubscribe")
headers = {'List-Unsubscribe': '<{unsubscribe_url}>'.format(unsubscribe_url=url)}
headers = {"List-Unsubscribe": "<{unsubscribe_url}>".format(unsubscribe_url=url)}
if message_id is not None:
headers['Message-ID'] = message_id
headers["Message-ID"] = message_id
# construct mails
mails = []
for recipient in set(recipients):
email = EmailMessage(subject, content, sender, [recipient], cc=cc,
headers=headers, **kwargs)
email = EmailMessage(
subject, content, sender, [recipient], cc=cc, headers=headers, **kwargs
)
if attachments is not None:
for attach in attachments:
email.attach_file(attach)
@ -50,15 +53,16 @@ def send(subject, content, sender, recipients, message_id=None, reply_to=None,
else:
succeeded = True
return NOT_SENT if failed and not succeeded else SENT if not failed\
and succeeded else PARTLY_SENT
return (
NOT_SENT if failed and not succeeded else SENT if not failed and succeeded else PARTLY_SENT
)
def get_content(content, registration_complete=True):
url = prepend_base_url("/newsletter/unsubscribe")
prepend = settings.PREPEND_INCOMPLETE_REGISTRATION_TEXT
text = "{prepend}{content}".format(prepend="" if registration_complete else prepend,
content=content)
text = "{prepend}{content}".format(
prepend="" if registration_complete else prepend, content=content
)
return text
@ -102,7 +106,9 @@ def get_invite_as_user_key(key):
def prepend_base_url(absolutelink):
return "{protocol}://{base}{link}".format(protocol=settings.PROTOCOL, base=settings.BASE_URL, link=absolutelink)
return "{protocol}://{base}{link}".format(
protocol=settings.PROTOCOL, base=settings.BASE_URL, link=absolutelink
)
def addr_with_name(addr, name):

@ -1,118 +1,140 @@
import logging
from django.db import models
from django.core.exceptions import ValidationError
import os
from contrib.models import CommonModel
from contrib.rules import has_global_perm
from django import forms
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import gettext
from .mailutils import send, get_content, NOT_SENT, SENT, PARTLY_SENT,\
addr_with_name
from django.utils.translation import gettext_lazy as _
from utils import RestrictedFileField
from jdav_web.celery import app
from django.core.validators import RegexValidator
from django.conf import settings
from contrib.rules import has_global_perm
from contrib.models import CommonModel
from .mailutils import addr_with_name
from .mailutils import get_content
from .mailutils import NOT_SENT
from .mailutils import PARTLY_SENT
from .mailutils import send
from .mailutils import SENT
from .rules import is_creator
import os
logger = logging.getLogger(__name__)
alphanumeric = RegexValidator(r'^[0-9a-zA-Z._-]*$',
_('Only alphanumeric characters, ., - and _ are allowed'))
alphanumeric = RegexValidator(
r"^[0-9a-zA-Z._-]*$", _("Only alphanumeric characters, ., - and _ are allowed")
)
class EmailAddress(models.Model):
"""Represents an email address, that is forwarded to specific members"""
name = models.CharField(_('name'), max_length=50, validators=[alphanumeric],
unique=True)
to_members = models.ManyToManyField('members.Member',
verbose_name=_('Forward to participants'),
blank=True)
to_groups = models.ManyToManyField('members.Group',
verbose_name=_('Forward to group'),
blank=True)
internal_only = models.BooleanField(verbose_name=_('Restrict to internal email addresses'),
help_text=_('Only allow forwarding to this e-mail address from one of the following domains: %(domains)s.') % {'domains': ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)},
default=False)
allowed_senders = models.ManyToManyField('members.Group',
verbose_name=_('Allowed sender'),
help_text=_('Only forward e-mails of members of selected groups. Leave empty to allow all senders.'),
blank=True,
related_name='allowed_sender_on_emailaddresses')
name = models.CharField(_("name"), max_length=50, validators=[alphanumeric], unique=True)
to_members = models.ManyToManyField(
"members.Member", verbose_name=_("Forward to participants"), blank=True
)
to_groups = models.ManyToManyField(
"members.Group", verbose_name=_("Forward to group"), blank=True
)
internal_only = models.BooleanField(
verbose_name=_("Restrict to internal email addresses"),
help_text=_(
"Only allow forwarding to this e-mail address from one of the following domains: %(domains)s."
)
% {"domains": ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)},
default=False,
)
allowed_senders = models.ManyToManyField(
"members.Group",
verbose_name=_("Allowed sender"),
help_text=_(
"Only forward e-mails of members of selected groups. Leave empty to allow all senders."
),
blank=True,
related_name="allowed_sender_on_emailaddresses",
)
@property
def email(self):
return "{0}@{1}".format(self.name, settings.DOMAIN)
return "{}@{}".format(self.name, settings.DOMAIN)
@property
def forwards(self):
mails = set(member.email for member in self.to_members.all())
mails.update([member.email for group in self.to_groups.all() for member in group.member_set.all()])
mails = {member.email for member in self.to_members.all()}
mails.update(
[member.email for group in self.to_groups.all() for member in group.member_set.all()]
)
return mails
def __str__(self):
return self.email
class Meta:
verbose_name = _('email address')
verbose_name_plural = _('email addresses')
verbose_name = _("email address")
verbose_name_plural = _("email addresses")
class EmailAddressForm(forms.ModelForm):
class Meta:
model = EmailAddress
exclude = []
def clean(self):
super(EmailAddressForm, self).clean()
group = self.cleaned_data.get('to_groups')
members = self.cleaned_data.get('to_members')
super().clean()
group = self.cleaned_data.get("to_groups")
members = self.cleaned_data.get("to_members")
if not group and not members:
raise ValidationError(_('Either a group or at least'
' one member is required as forward recipient.'))
raise ValidationError(
_("Either a group or at least one member is required as forward recipient.")
)
# Create your models here.
class Message(CommonModel):
"""Represents a message that can be sent to some members"""
subject = models.CharField(_('subject'), max_length=50)
content = models.TextField(_('content'))
to_groups = models.ManyToManyField('members.Group',
verbose_name=_('to group'),
blank=True)
to_freizeit = models.ForeignKey('members.Freizeit',
verbose_name=_('to freizeit'),
on_delete=models.CASCADE,
blank=True,
null=True)
to_notelist = models.ForeignKey('members.MemberNoteList',
verbose_name=_('to notes list'),
on_delete=models.CASCADE,
blank=True,
null=True)
to_members = models.ManyToManyField('members.Member',
verbose_name=_('to member'),
blank=True)
reply_to = models.ManyToManyField('members.Member',
verbose_name=_('reply to participant'),
blank=True,
related_name='reply_to')
reply_to_email_address = models.ManyToManyField('mailer.EmailAddress',
verbose_name=_('reply to custom email address'),
blank=True,
related_name='reply_to_email_addr')
sent = models.BooleanField(_('sent'), default=False)
created_by = models.ForeignKey('members.Member', verbose_name=_('Created by'),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='created_messages')
subject = models.CharField(_("subject"), max_length=50)
content = models.TextField(_("content"))
to_groups = models.ManyToManyField("members.Group", verbose_name=_("to group"), blank=True)
to_freizeit = models.ForeignKey(
"members.Freizeit",
verbose_name=_("to freizeit"),
on_delete=models.CASCADE,
blank=True,
null=True,
)
to_notelist = models.ForeignKey(
"members.MemberNoteList",
verbose_name=_("to notes list"),
on_delete=models.CASCADE,
blank=True,
null=True,
)
to_members = models.ManyToManyField("members.Member", verbose_name=_("to member"), blank=True)
reply_to = models.ManyToManyField(
"members.Member",
verbose_name=_("reply to participant"),
blank=True,
related_name="reply_to",
)
reply_to_email_address = models.ManyToManyField(
"mailer.EmailAddress",
verbose_name=_("reply to custom email address"),
blank=True,
related_name="reply_to_email_addr",
)
sent = models.BooleanField(_("sent"), default=False)
created_by = models.ForeignKey(
"members.Member",
verbose_name=_("Created by"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="created_messages",
)
def __str__(self):
return self.subject
@ -126,9 +148,10 @@ class Message(CommonModel):
if 3 > self.to_members.count() > 0:
recipients.extend([m.name for m in self.to_members.all()])
elif self.to_members.count() > 2:
recipients.append(gettext('Some other members'))
recipients.append(gettext("Some other members"))
return ", ".join(recipients)
get_recipients.short_description = _('recipients')
get_recipients.short_description = _("recipients")
def submit(self, sender=None):
"""Sends the mail to the specified group of members"""
@ -141,24 +164,21 @@ class Message(CommonModel):
members.update(self.to_members.all())
# get all the members of the selected freizeit
if self.to_freizeit is not None:
members.update([mol.member for mol in
self.to_freizeit.membersonlist.all()])
members.update([mol.member for mol in self.to_freizeit.membersonlist.all()])
members.update(self.to_freizeit.jugendleiter.all())
# get all the members of the selected notes list
if self.to_notelist is not None:
members.update([mol.member for mol in
self.to_notelist.membersonlist.all()])
members.update([mol.member for mol in self.to_notelist.membersonlist.all()])
filtered = [m for m in members if m.gets_newsletter]
logger.info(f"sending mail to {filtered}")
attach = [a.f.path for a in Attachment.objects.filter(msg__id=self.pk)
if a.f.name]
attach = [a.f.path for a in Attachment.objects.filter(msg__id=self.pk) if a.f.name]
emails = [member.email for member in filtered]
emails.extend([member.alternative_email for member in filtered if member.alternative_email])
# remove any underscores from subject to prevent Arne from using
# terrible looking underscores in subjects
self.subject = self.subject.replace('_', ' ')
self.subject = self.subject.replace("_", " ")
# generate message id
message_id = "<{pk}@{domain}>".format(pk=self.pk, domain=settings.DOMAIN)
# reply to addresses
@ -176,15 +196,23 @@ class Message(CommonModel):
# if sending from the association email has been disabled,
# a sender was supplied and the reply to is empty, add the sender's
# DAV360 email as reply to
if sender and not settings.SEND_FROM_ASSOCIATION_EMAIL and sender.has_internal_email() and reply_to == []:
if (
sender
and not settings.SEND_FROM_ASSOCIATION_EMAIL
and sender.has_internal_email()
and reply_to == []
):
reply_to.append(addr_with_name(sender.email, sender.name))
try:
success = send(self.subject, get_content(self.content, registration_complete=True),
from_addr,
emails,
message_id=message_id,
attachments=attach,
reply_to=reply_to)
success = send(
self.subject,
get_content(self.content, registration_complete=True),
from_addr,
emails,
message_id=message_id,
attachments=attach,
reply_to=reply_to,
)
if success == SENT or success == PARTLY_SENT:
self.sent = True
for a in Attachment.objects.filter(msg__id=self.pk):
@ -200,51 +228,48 @@ class Message(CommonModel):
return success
class Meta(CommonModel.Meta):
verbose_name = _('message')
verbose_name_plural = _('messages')
permissions = (
("submit_mails", _("Can submit mails")),
)
verbose_name = _("message")
verbose_name_plural = _("messages")
permissions = (("submit_mails", _("Can submit mails")),)
rules_permissions = {
"view_obj": is_creator | has_global_perm('mailer.view_global_message'),
"change_obj": is_creator | has_global_perm('mailer.change_global_message'),
"delete_obj": is_creator | has_global_perm('mailer.delete_global_message'),
"view_obj": is_creator | has_global_perm("mailer.view_global_message"),
"change_obj": is_creator | has_global_perm("mailer.change_global_message"),
"delete_obj": is_creator | has_global_perm("mailer.delete_global_message"),
}
class MessageForm(forms.ModelForm):
class Meta:
model = Message
exclude = []
def clean(self):
group = self.cleaned_data.get('to_groups')
freizeit = self.cleaned_data.get('to_freizeit')
notelist = self.cleaned_data.get('to_notelist')
members = self.cleaned_data.get('to_members')
group = self.cleaned_data.get("to_groups")
freizeit = self.cleaned_data.get("to_freizeit")
notelist = self.cleaned_data.get("to_notelist")
members = self.cleaned_data.get("to_members")
if not group and freizeit is None and not members and notelist is None:
raise ValidationError(_('Either a group, a memberlist or at least'
' one member is required as recipient'))
raise ValidationError(
_("Either a group, a memberlist or at least one member is required as recipient")
)
class Attachment(CommonModel):
"""Represents an attachment to an email"""
msg = models.ForeignKey(Message, on_delete=models.CASCADE)
# file (not naming it file because of builtin)
f = RestrictedFileField(_('file'),
upload_to='attachments',
max_upload_size=10)
f = RestrictedFileField(_("file"), upload_to="attachments", max_upload_size=10)
def __str__(self):
return os.path.basename(self.f.name) if self.f.name else str(_("Empty"))
class Meta:
verbose_name = _('attachment')
verbose_name_plural = _('attachments')
verbose_name = _("attachment")
verbose_name_plural = _("attachments")
rules_permissions = {
"add_obj": is_creator | has_global_perm('mailer.view_global_message'),
"view_obj": is_creator | has_global_perm('mailer.view_global_message'),
"change_obj": is_creator | has_global_perm('mailer.change_global_message'),
"delete_obj": is_creator | has_global_perm('mailer.delete_global_message'),
"add_obj": is_creator | has_global_perm("mailer.view_global_message"),
"view_obj": is_creator | has_global_perm("mailer.view_global_message"),
"change_obj": is_creator | has_global_perm("mailer.change_global_message"),
"delete_obj": is_creator | has_global_perm("mailer.delete_global_message"),
}

@ -1,6 +1,7 @@
from contrib.rules import memberize_user
from rules import predicate
@predicate
@memberize_user
def is_creator(self, message):

@ -4,6 +4,6 @@ from . import views
app_name = "mailer"
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^unsubscribe', views.unsubscribe, name='unsubscribe'),
re_path(r"^$", views.index, name="index"),
re_path(r"^unsubscribe", views.unsubscribe, name="unsubscribe"),
]

@ -1,57 +1,59 @@
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django import forms
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.http import HttpResponseRedirect
from .mailutils import send as send_mail, get_unsubscribe_link
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from members.models import Member
from .mailutils import get_unsubscribe_link
from .mailutils import send as send_mail
def index(request):
return HttpResponseRedirect(reverse('mailer:unsubscribe'))
return HttpResponseRedirect(reverse("mailer:unsubscribe"))
def render_unsubscribe(request, error_message=""):
context = {}
if error_message:
context['error_message'] = error_message
return render(request, 'mailer/unsubscribe.html', context)
context["error_message"] = error_message
return render(request, "mailer/unsubscribe.html", context)
def render_unsubscribed(request, email):
return render(request, 'mailer/unsubscribed.html', {'email': email})
return render(request, "mailer/unsubscribed.html", {"email": email})
def unsubscribe(request):
if request.method == 'GET' and 'key' in request.GET:
if request.method == "GET" and "key" in request.GET:
try:
key = request.GET['key']
key = request.GET["key"]
member = Member.objects.get(unsubscribe_key=key)
if not member.unsubscribe(key):
raise KeyError
except (KeyError, Member.DoesNotExist):
return render_unsubscribe(request,
_("Can't verify this link. Try again!"))
return render_unsubscribe(request, _("Can't verify this link. Try again!"))
else:
return render_unsubscribed(request, member.email)
elif not request.POST.get('post', False):
elif not request.POST.get("post", False):
# just calling up unsubscribe page
return render_unsubscribe(request)
try:
email = request.POST['email']
email = request.POST["email"]
member = Member.objects.filter(email=email).first()
if not member: # member not found
raise KeyError
except (KeyError, Member.DoesNotExist):
return render_unsubscribe(request, _("Please fill in every field"))
else:
send_mail(_("Unsubscription confirmation"),
settings.UNSUBSCRIBE_CONFIRMATION_TEXT.format(link=get_unsubscribe_link(member)),
settings.DEFAULT_SENDING_MAIL, email)
send_mail(
_("Unsubscription confirmation"),
settings.UNSUBSCRIBE_CONFIRMATION_TEXT.format(link=get_unsubscribe_link(member)),
settings.DEFAULT_SENDING_MAIL,
email,
)
return render_confirmation_sent(request, email)
def render_confirmation_sent(request, email):
return render(request, 'mailer/confirmation_sent.html', {'email': email})
return render(request, "mailer/confirmation_sent.html", {"email": email})

Loading…
Cancel
Save