summaryrefslogtreecommitdiff
path: root/app/lib/contact/forms.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/lib/contact/forms.py')
-rw-r--r--app/lib/contact/forms.py145
1 files changed, 145 insertions, 0 deletions
diff --git a/app/lib/contact/forms.py b/app/lib/contact/forms.py
new file mode 100644
index 0000000..cf905ac
--- /dev/null
+++ b/app/lib/contact/forms.py
@@ -0,0 +1,145 @@
+"""
+A base contact form for allowing users to send email messages through
+a web interface.
+
+"""
+
+from typing import Any, Dict, List, Optional
+
+from django import forms, http
+from django.conf import settings
+from django.contrib.sites.shortcuts import get_current_site
+from django.core.mail import send_mail
+from django.template import loader
+from django.utils.translation import gettext_lazy as _
+
+# Parameters to a form being handled from a live request will actually
+# be instances of django.utils.datastructures.MultiValueDict, but this
+# is declared as a typing.Dict for a few reasons:
+#
+# * MultiValueDict is a subclass of dict, so instances of
+# MultiValueDict will pass type checks.
+#
+# * Testing of forms more typically passes in plain dict for the form
+# arguments, so supporting it is useful.
+#
+# * PEP 560, which allows dropping the typing module's aliases and
+# subscripting the actual types, was adopted for Python 3.7, but
+# currently we support back to Python 3.5.
+StringKeyedDict = Dict[str, Any]
+
+
+class ContactForm(forms.Form):
+ """
+ The base contact form class from which all contact form classes
+ should inherit.
+
+ """
+
+ name = forms.CharField(max_length=100, label=_("Your name"))
+ email = forms.EmailField(max_length=200, label=_("Your email address"))
+ body = forms.CharField(widget=forms.Textarea, label=_("Your message"))
+
+ from_email = settings.DEFAULT_FROM_EMAIL
+
+ recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]
+
+ subject_template_name = "contact/contact_form_subject.txt"
+
+ template_name = "contact/contact_form.txt"
+
+ def __init__(
+ self,
+ data: Optional[StringKeyedDict] = None,
+ files: Optional[StringKeyedDict] = None,
+ request: Optional[http.HttpRequest] = None,
+ recipient_list: Optional[List[str]] = None,
+ *args,
+ **kwargs
+ ):
+ if request is None:
+ raise TypeError("Keyword argument 'request' must be supplied")
+ self.request = request
+ if recipient_list is not None:
+ self.recipient_list = recipient_list
+ super().__init__(data=data, files=files, *args, **kwargs)
+
+ def message(self) -> str:
+ """
+ Render the body of the message to a string.
+
+ """
+ template_name = (
+ self.template_name() if callable(self.template_name) else self.template_name
+ )
+ return loader.render_to_string(
+ template_name, self.get_context(), request=self.request
+ )
+
+ def subject(self) -> str:
+ """
+ Render the subject of the message to a string.
+
+ """
+ template_name = (
+ self.subject_template_name()
+ if callable(self.subject_template_name)
+ else self.subject_template_name
+ )
+ subject = loader.render_to_string(
+ template_name, self.get_context(), request=self.request
+ )
+ return "".join(subject.splitlines())
+
+ def get_context(self) -> StringKeyedDict:
+ """
+ Return the context used to render the templates for the email
+ subject and body.
+
+ By default, this context includes:
+
+ * All of the validated values in the form, as variables of the
+ same names as their fields.
+
+ * The current ``Site`` object, as the variable ``site``.
+
+ * Any additional variables added by context processors (this
+ will be a ``RequestContext``).
+
+ """
+ if not self.is_valid():
+ raise ValueError("Cannot generate Context from invalid contact form")
+ return dict(self.cleaned_data, site=get_current_site(self.request))
+
+ def get_message_dict(self) -> StringKeyedDict:
+ """
+ Generate the various parts of the message and return them in a
+ dictionary, suitable for passing directly as keyword arguments
+ to ``django.core.mail.send_mail()``.
+
+ By default, the following values are returned:
+
+ * ``from_email``
+
+ * ``message``
+
+ * ``recipient_list``
+
+ * ``subject``
+
+ """
+ if not self.is_valid():
+ raise ValueError("Message cannot be sent from invalid contact form")
+ message_dict = {}
+ for message_part in ("from_email", "message", "recipient_list", "subject"):
+ attr = getattr(self, message_part)
+ message_dict[message_part] = attr() if callable(attr) else attr
+ return message_dict
+
+ def save(self, fail_silently: bool = False) -> None:
+ """
+ Build and send the email message.
+
+ """
+ send_mail(fail_silently=fail_silently, **self.get_message_dict())
+