diff --git a/website/thaliawebsite/forms.py b/website/thaliawebsite/forms.py index ee71822ad..fa74686ea 100644 --- a/website/thaliawebsite/forms.py +++ b/website/thaliawebsite/forms.py @@ -1,5 +1,10 @@ """Special forms.""" +from django import forms +from django.conf import settings from django.contrib.auth.forms import AuthenticationForm as BaseAuthenticationForm +from django.utils.translation import gettext_lazy as _ + +from utils.snippets import send_email class AuthenticationForm(BaseAuthenticationForm): @@ -10,3 +15,87 @@ def clean(self): if "username" in self.cleaned_data: self.cleaned_data["username"] = self.cleaned_data["username"].lower() super().clean() + + +class EmailSenderForm(forms.Form): + recipients = forms.CharField( + label=_("Recipients"), + help_text=_("Enter multiple email addresses separated by commas."), + required=True, + ) + cc_recipients = forms.CharField( + label=_("CC recipients (optional)"), + help_text=_("Enter multiple email addresses separated by commas."), + required=False, + ) + bcc_recipients = forms.CharField( + label=_("BCC recipients (optional)"), + help_text=_( + "Enter multiple email addresses separated by commas. Emails will always be sent to %s as well." + ) + % settings.DEFAULT_FROM_EMAIL, + required=False, + ) + + reply_to = forms.CharField( + label=_("Reply to-addresses (optional)"), + help_text=_( + "Enter multiple email addresses separated by commas. Will be set as reply-to email address. " + "Emails will always be sent from %s." + ) + % settings.DEFAULT_FROM_EMAIL, + required=False, + ) + + subject = forms.CharField(label=_("Subject")) + message = forms.CharField( + label=_("Message body"), + help_text=_("Supports or simple HTML (but be careful with that)."), + widget=forms.Textarea, + ) + + def clean_recipients(self): + return self.email_address_cleaner("recipients") + + def clean_bcc_recipients(self): + return self.email_address_cleaner("bcc_recipients") + + def clean_cc_recipients(self): + return self.email_address_cleaner("cc_recipients") + + def clean_reply_to(self): + return self.email_address_cleaner("reply_to") + + def email_address_cleaner(self, field_name): + recipients = self.cleaned_data[field_name].split(",") + recipients = [r.strip() for r in recipients] + + if recipients == [""]: + return None + + # validate that all recipients are valid email addresses + for recipient in recipients: + if not forms.EmailField().clean(recipient): + raise forms.ValidationError( + _("Invalid email address: %(email)s"), params={"email": recipient} + ) + + return recipients + + def send(self): + txt_template = "email/email_sender_email.txt" + html_template = "email/email_sender_email.html" + + bcc = self.cleaned_data["bcc_recipients"] or [] + bcc += settings.DEFAULT_FROM_EMAIL + + send_email( + to=self.cleaned_data["recipients"], + subject=self.cleaned_data["subject"], + txt_template=txt_template, + context={"message": self.cleaned_data["message"]}, + html_template=html_template, + bcc=bcc, + cc=self.cleaned_data["cc_recipients"], + reply_to=self.cleaned_data["reply_to"], + ) diff --git a/website/thaliawebsite/migrations/0001_initial.py b/website/thaliawebsite/migrations/0001_initial.py new file mode 100644 index 000000000..2bc1d9f0e --- /dev/null +++ b/website/thaliawebsite/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.1 on 2023-10-02 18:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="FunctionalPermissions", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ], + options={ + "permissions": ( + ("email_sender", "Send emails using the email sender"), + ), + "managed": False, + "default_permissions": (), + }, + ), + ] diff --git a/website/thaliawebsite/models.py b/website/thaliawebsite/models.py new file mode 100644 index 000000000..f3159b5a8 --- /dev/null +++ b/website/thaliawebsite/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class FunctionalPermissions(models.Model): + """Custom auxiliary model to define functional non-model permissions.""" + + class Meta: + managed = False # Don't create a table for this model + default_permissions = () # disable "add", "change", "delete" and "view" default permissions + permissions = (("email_sender", "Send emails using the email sender"),) + + def __str__(self): + return "Functional permissions" diff --git a/website/thaliawebsite/templates/base.html b/website/thaliawebsite/templates/base.html index 7ce657f7a..432e186b2 100644 --- a/website/thaliawebsite/templates/base.html +++ b/website/thaliawebsite/templates/base.html @@ -188,6 +188,14 @@ {% trans "Site administration" %} + {% if perms.thaliawebsite.email_sender %} +
This email was automatically generated.
+ {% endblock %}