diff options
Diffstat (limited to 'app/lib/contact')
-rw-r--r-- | app/lib/contact/__init__.py | 104 | ||||
-rw-r--r-- | app/lib/contact/forms.py | 145 | ||||
-rw-r--r-- | app/lib/contact/templates/contact/contact_form.html | 28 | ||||
-rw-r--r-- | app/lib/contact/templates/contact/contact_form.txt | 0 | ||||
-rw-r--r-- | app/lib/contact/templates/contact/contact_form_sent.html | 14 | ||||
-rw-r--r-- | app/lib/contact/templates/contact/contact_form_subject.txt | 1 | ||||
-rw-r--r-- | app/lib/contact/urls.py | 22 | ||||
-rw-r--r-- | app/lib/contact/views.py | 45 |
8 files changed, 359 insertions, 0 deletions
diff --git a/app/lib/contact/__init__.py b/app/lib/contact/__init__.py new file mode 100644 index 0000000..e4c5943 --- /dev/null +++ b/app/lib/contact/__init__.py @@ -0,0 +1,104 @@ +from importlib import import_module + +from django.apps import apps +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse # Django < 1.10 + + +DEFAULT_COMMENTS_APP = 'django_comments' + + +def get_comment_app(): + """ + Get the comment app (i.e. "django_comments") as defined in the settings + """ + # Make sure the app's in INSTALLED_APPS + comments_app = get_comment_app_name() + if not apps.is_installed(comments_app): + raise ImproperlyConfigured( + "The COMMENTS_APP (%r) must be in INSTALLED_APPS" % comments_app + ) + + # Try to import the package + try: + package = import_module(comments_app) + except ImportError as e: + raise ImproperlyConfigured( + "The COMMENTS_APP setting refers to a non-existing package. (%s)" % e + ) + + return package + + +def get_comment_app_name(): + """ + Returns the name of the comment app (either the setting value, if it + exists, or the default). + """ + return getattr(settings, 'COMMENTS_APP', DEFAULT_COMMENTS_APP) + + +def get_model(): + """ + Returns the comment model class. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_model"): + return get_comment_app().get_model() + else: + from django_comments.models import Comment + return Comment + + +def get_form(): + from django_comments.forms import CommentForm + """ + Returns the comment ModelForm class. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_form"): + return get_comment_app().get_form() + else: + return CommentForm + + +def get_form_target(): + """ + Returns the target URL for the comment form submission view. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_form_target"): + return get_comment_app().get_form_target() + else: + return reverse("comments-post-comment") + + +def get_flag_url(comment): + """ + Get the URL for the "flag this comment" view. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_flag_url"): + return get_comment_app().get_flag_url(comment) + else: + return reverse("comments-flag", args=(comment.id,)) + + +def get_delete_url(comment): + """ + Get the URL for the "delete this comment" view. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_delete_url"): + return get_comment_app().get_delete_url(comment) + else: + return reverse("comments-delete", args=(comment.id,)) + + +def get_approve_url(comment): + """ + Get the URL for the "approve this comment from moderation" view. + """ + if get_comment_app_name() != DEFAULT_COMMENTS_APP and hasattr(get_comment_app(), "get_approve_url"): + return get_comment_app().get_approve_url(comment) + else: + return reverse("comments-approve", args=(comment.id,)) 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()) + diff --git a/app/lib/contact/templates/contact/contact_form.html b/app/lib/contact/templates/contact/contact_form.html new file mode 100644 index 0000000..1be2c27 --- /dev/null +++ b/app/lib/contact/templates/contact/contact_form.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% load html5_datetime %} +{%block bodyid%}id="{{object.title|slugify}}"{%endblock%} +{% block breadcrumbs %}{% include "lib/breadcrumbs.html" with breadcrumbs=breadcrumbs %}{% endblock %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <div class="archive-intro"> + <h2 class="archive-hed">Contact Information</h2> + <p>Want to know more about private tutoring or group classes? Email me: <a href="mailto:corrinne@cumuluslearning.net">corrinne@cumuluslearning.net</a></p> + + <p>Or you can use the form below to get in touch.</p> + <form action="" method="post" class="comment-form contact-form card-subscribe">{% csrf_token %} + {% for field in form %} + <fieldset> + {{field.label_tag}} + {%if field.name == "body"%}<div class="textarea-rounded">{{ field }}</div>{%else%}{{field}}{%endif%} + </fieldset> + {% if forloop.last %}<input type="submit" name="post" class="btn" value="Send" />{%endif%} + <small class="alert">{% if field.errors %}{{field.errors}}{% endif %}</small> + {%endfor%} + </form> + </div> + </main> +</main> +{% endblock %} + +{{body}} diff --git a/app/lib/contact/templates/contact/contact_form.txt b/app/lib/contact/templates/contact/contact_form.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/lib/contact/templates/contact/contact_form.txt diff --git a/app/lib/contact/templates/contact/contact_form_sent.html b/app/lib/contact/templates/contact/contact_form_sent.html new file mode 100644 index 0000000..668224e --- /dev/null +++ b/app/lib/contact/templates/contact/contact_form_sent.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% load html5_datetime %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <div class="archive-intro"> + <h2 class="archive-hed">Thank You</h2> + <p>I'll get back to you very soon.</p> + </div> + </main> +</main> +{% endblock %} + +{{body}} diff --git a/app/lib/contact/templates/contact/contact_form_subject.txt b/app/lib/contact/templates/contact/contact_form_subject.txt new file mode 100644 index 0000000..498c81f --- /dev/null +++ b/app/lib/contact/templates/contact/contact_form_subject.txt @@ -0,0 +1 @@ +[Cumulus Learning] diff --git a/app/lib/contact/urls.py b/app/lib/contact/urls.py new file mode 100644 index 0000000..080b33c --- /dev/null +++ b/app/lib/contact/urls.py @@ -0,0 +1,22 @@ +""" +Example URLConf for a contact form. + +If all you want is the basic ContactForm with default behavior, +include this URLConf somewhere in your URL hierarchy (for example, at +``/contact/``) + +""" + +from django.urls import path +from django.views.generic import TemplateView + +from contact.views import ContactFormView + +urlpatterns = [ + path("", ContactFormView.as_view(), name="contact_form"), + path( + "sent/", + TemplateView.as_view(template_name="contact/contact_form_sent.html"), + name="contact_form_sent", + ), +] diff --git a/app/lib/contact/views.py b/app/lib/contact/views.py new file mode 100644 index 0000000..9e2c9c7 --- /dev/null +++ b/app/lib/contact/views.py @@ -0,0 +1,45 @@ +""" +View which can render and send email from a contact form. + +""" + +from django import http +from django.urls import reverse_lazy +from django.views.generic.edit import FormView + +from .forms import ContactForm, StringKeyedDict + + +class ContactFormView(FormView): + form_class = ContactForm + recipient_list = None + success_url = reverse_lazy("contact_form_sent") + template_name = "contact/contact_form.html" + + def get_context_data(self, **kwargs): + ''' + Adds breadcrumb path to every view + ''' + # Call the base implementation first to get a context + + context = super().get_context_data(**kwargs) + # special case for pages: + context['breadcrumbs'] = ('contact',) + context['crumb_url'] = None + return context + + def form_valid(self, form) -> http.HttpResponse: + form.save() + return super().form_valid(form) + + def get_form_kwargs(self) -> StringKeyedDict: + # ContactForm instances require instantiation with an + # HttpRequest. + kwargs = super().get_form_kwargs() + kwargs.update({"request": self.request}) + + # We may also have been given a recipient list when + # instantiated. + if self.recipient_list is not None: + kwargs.update({"recipient_list": self.recipient_list}) + return kwargs |