summaryrefslogtreecommitdiff
path: root/app/lib/contact
diff options
context:
space:
mode:
Diffstat (limited to 'app/lib/contact')
-rw-r--r--app/lib/contact/__init__.py104
-rw-r--r--app/lib/contact/forms.py145
-rw-r--r--app/lib/contact/templates/contact/contact_form.html28
-rw-r--r--app/lib/contact/templates/contact/contact_form.txt0
-rw-r--r--app/lib/contact/templates/contact/contact_form_sent.html14
-rw-r--r--app/lib/contact/templates/contact/contact_form_subject.txt1
-rw-r--r--app/lib/contact/urls.py22
-rw-r--r--app/lib/contact/views.py45
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