Skip to content

Commit 588a0ba

Browse files
msomiericksolarissmoke
authored andcommittedSep 19, 2017
Feature/contact (#1)
* add contact funcionality * update repo url * rename template folder to app dir * rename template folder to app dir * ignore *.egg-info * update setup.py, README and MANIFEST * remove unused files * cleanup forms and mixins and use bleach library * add enquiry_email field to the ContentMixin * add tests * update app name in app config * add a standalone test runner * add more tests
1 parent 2ccf040 commit 588a0ba

17 files changed

+345
-0
lines changed
 

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
*.egg-info
3+
__pycache__

‎MANIFEST.in

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include LICENSE
2+
include README.rst
3+
recursive-include wagtailcontact/templates *

‎README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Provide a simple mixin which bakes a contact form(send emails on submit) into the inheriting classes, specifically Wagtail pages.
2+
3+
The mixin is an abstract Django model which has an enquiry_email field.
4+
This field is also added to the content_panels.
5+
6+
### Install
7+
`pip install https://github.com/regulusweb/wagtail-contact/archive/master.zip`
8+
9+
Be sure to add `honeypot` and `wagtailcontact` to INSTALLED_APPS in settings.py.
10+
The contact form uses `django-honeypot` to prevent automated form spam.
11+
12+
13+
`django-honeypot` also requires a few settings to be declared in settings.py.
14+
See [django-honeypot](https://github.com/jamesturk/django-honeypot) for more information.
15+
16+
17+
### Usage
18+
The Wagtail page you want to add this functionality should extend
19+
`wagtailcontact.mixins.ContactMixin`.
20+
21+
When rendering the form in your page template be sure to add the honeypot field using the
22+
`render_honeypot_field` tag. Otherwise a HTTP BadRequest will explode when you try to submit the form without this.
23+
24+
Like so:
25+
26+
`{% load honeypot %}`
27+
28+
And then within the form include the honeypot field:
29+
30+
`{% render_honeypot_field %}`
31+
32+
### Tests
33+
You can either invoke `python runtests.py`, or `python setup.py test`

‎runtests.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env python
2+
import os
3+
import sys
4+
5+
import django
6+
from django.conf import settings
7+
from django.test.utils import get_runner
8+
9+
10+
def run_tests():
11+
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
12+
django.setup()
13+
TestRunner = get_runner(settings)
14+
test_runner = TestRunner()
15+
failures = test_runner.run_tests(["tests"])
16+
sys.exit(bool(failures))
17+
18+
19+
if __name__ == "__main__":
20+
run_tests()

‎setup.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
from setuptools import find_packages, setup
3+
4+
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
5+
README = readme.read()
6+
7+
# allow setup.py to be run from any path
8+
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
9+
10+
setup(
11+
name='wagtail-contact',
12+
version='0.1',
13+
packages=find_packages(),
14+
test_suite='runtests.run_tests',
15+
include_package_data=True,
16+
license='MIT License',
17+
description='A simple Wagtail contact app.',
18+
long_description=README,
19+
url='https://github.com/regulusweb/wagtail-contact',
20+
author='Regulus Ltd',
21+
author_email='info@regulusweb.com',
22+
install_requires=[
23+
'django-honeypot==0.6.0',
24+
'bleach==2.0.0',
25+
'wagtail>=1.12'
26+
],
27+
classifiers=[
28+
'Environment :: Web Environment',
29+
'Framework :: Django',
30+
'Framework :: Django :: 1.11',
31+
'Intended Audience :: Developers',
32+
'License :: OSI Approved :: MIT License',
33+
'Operating System :: OS Independent',
34+
'Programming Language :: Python',
35+
'Programming Language :: Python :: 3',
36+
'Programming Language :: Python :: 3.5',
37+
'Programming Language :: Python :: 3.6',
38+
'Topic :: Internet :: WWW/HTTP',
39+
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
40+
],
41+
)

‎tests/__init__.py

Whitespace-only changes.

‎tests/test_forms.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from unittest.mock import Mock
2+
3+
from django.core import mail
4+
from django.test import TestCase
5+
6+
from wagtailcontact.forms import ContactForm
7+
8+
9+
class ContactFormTestCase(TestCase):
10+
11+
valid_data = {'name': 'test', 'email': 't@t.com', 'message': 'Hi'}
12+
13+
def test_required_fields(self):
14+
form = ContactForm()
15+
self.assertTrue(
16+
all([
17+
form.fields['email'].required,
18+
form.fields['name'].required,
19+
form.fields['message'].required
20+
])
21+
)
22+
23+
def test_email_subject_prefix_added_to_ctx(self):
24+
form = ContactForm(data=self.valid_data.copy())
25+
form.is_valid()
26+
ctx = form.get_email_context()
27+
self.assertEqual(ctx['subject_prefix'], 'Admin')
28+
29+
def test_form_valid(self):
30+
form = ContactForm(data=self.valid_data.copy())
31+
self.assertTrue(form.is_valid())
32+
33+
def test_send_email(self):
34+
data = self.valid_data.copy()
35+
data['message'] = 'Hello good people'
36+
form = ContactForm(data=data)
37+
form.is_valid() # trigger to populate cleaned_data
38+
reply_to = [form.cleaned_data['email']]
39+
form.send_email(['to@g.com'], form.subject_template, form.txt_template, reply_to)
40+
41+
self.assertEqual(len(mail.outbox), 1)
42+
self.assertIn('Hello good people', mail.outbox[0].body)
43+
44+
def test_go_to_with_enquiryemail(self):
45+
form = ContactForm()
46+
page = Mock()
47+
page.enquiry_email = 'e@e.com'
48+
to = form.get_to(page)
49+
self.assertEqual(to, ['e@e.com'])
50+
51+
def test_go_to_without_enquiryemail(self):
52+
form = ContactForm()
53+
page = Mock()
54+
page.enquiry_email = ''
55+
to = form.get_to(page)
56+
self.assertEqual(to, ['admin@localhost.com'])
57+
58+
def test_save(self):
59+
form = ContactForm()
60+
form.cleaned_data = self.valid_data.copy()
61+
page = Mock()
62+
page.enquiry_email = 'e@e.com'
63+
form.save(page)
64+
self.assertEqual(len(mail.outbox), 1)

‎tests/test_settings.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import os
2+
3+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
4+
5+
SECRET_KEY = 'secret'
6+
INSTALLED_APPS = [
7+
"wagtailcontact",
8+
]
9+
10+
DATABASES = {
11+
'default': {
12+
'ENGINE': 'django.db.backends.sqlite3',
13+
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
14+
}
15+
}
16+
17+
18+
TEMPLATES = [
19+
{
20+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
21+
'DIRS': [],
22+
'APP_DIRS': True,
23+
'OPTIONS': {
24+
'context_processors': [
25+
'django.template.context_processors.debug',
26+
'django.template.context_processors.request',
27+
'django.contrib.auth.context_processors.auth',
28+
'django.contrib.messages.context_processors.messages',
29+
],
30+
},
31+
},
32+
]
33+
34+
MANAGERS = [
35+
('admin', 'admin@localhost.com')
36+
]
37+
38+
EMAIL_SUBJECT_PREFIX = 'Admin'

‎tests/test_tags.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from django.test import TestCase
2+
3+
from wagtailcontact.templatetags.wagtailcontact_tags import bleachclean
4+
5+
6+
class WagtailContactTagsTestCase(TestCase):
7+
8+
def test_bleanclean_cleandata(self):
9+
cleaned = bleachclean('Hello')
10+
self.assertEqual(cleaned, 'Hello')
11+
12+
def test_bleanclean_strips(self):
13+
cleaned = bleachclean('<script>evil</script>')
14+
self.assertEqual(cleaned, 'evil')

‎wagtailcontact/__init__.py

Whitespace-only changes.

‎wagtailcontact/apps.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class ContactConfig(AppConfig):
5+
name = 'wagtailcontact'

‎wagtailcontact/forms.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from django import forms
2+
from django.conf import settings
3+
from django.core.mail import EmailMessage
4+
from django.template import loader
5+
6+
7+
class ContactForm(forms.Form):
8+
9+
subject_template = "wagtailcontact/email/subject.txt"
10+
txt_template = "wagtailcontact/email/message.txt"
11+
12+
name = forms.CharField(max_length=100, required=True)
13+
email = forms.EmailField(required=True)
14+
message = forms.CharField(
15+
widget=forms.Textarea(attrs={'rows': 4, 'cols': False}),
16+
required=True,
17+
)
18+
19+
def get_email_context(self):
20+
ctx = self.cleaned_data
21+
ctx['subject_prefix'] = settings.EMAIL_SUBJECT_PREFIX
22+
return ctx
23+
24+
def send_email(self, to, subject_template, txt_template, reply_to):
25+
context = self.get_email_context()
26+
from_email = settings.DEFAULT_FROM_EMAIL
27+
subject = loader.render_to_string(subject_template, context)
28+
message = loader.render_to_string(txt_template, context)
29+
msg = EmailMessage(
30+
subject.strip(),
31+
message,
32+
from_email,
33+
to,
34+
reply_to=reply_to
35+
)
36+
msg.send()
37+
38+
def get_to(self, page):
39+
if page.enquiry_email:
40+
return [page.enquiry_email]
41+
else:
42+
return [e for _, e in settings.MANAGERS]
43+
44+
def save(self, page):
45+
# Forward enquiry
46+
to = self.get_to(page)
47+
reply_to = [self.cleaned_data['email']]
48+
self.send_email(to, self.subject_template, self.txt_template, reply_to)

‎wagtailcontact/mixins.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from django.contrib import messages
2+
from django.db import models
3+
from django.http import HttpResponseRedirect
4+
from django.utils.decorators import method_decorator
5+
6+
from honeypot.decorators import check_honeypot
7+
from wagtail.wagtailadmin.edit_handlers import FieldPanel
8+
from wagtail.wagtailcore.models import Page
9+
10+
from .forms import ContactForm
11+
12+
13+
class ContactMixin(models.Model):
14+
form_class = ContactForm
15+
success_url = None
16+
success_message = 'Thank you! We will get back to you as soon as possible.'
17+
18+
enquiry_email = models.EmailField(
19+
blank=True,
20+
help_text="Email address that will receive enquiries",
21+
null=True,
22+
)
23+
24+
content_panels = Page.content_panels + [
25+
FieldPanel('enquiry_email'),
26+
]
27+
28+
class Meta:
29+
abstract = True
30+
31+
def get_form(self, request):
32+
return self.form_class(request.POST or None)
33+
34+
@method_decorator(check_honeypot)
35+
def serve(self, request, *args, **kwargs):
36+
self.form = self.get_form(request)
37+
if request.method == 'POST':
38+
if self.form.is_valid():
39+
self.form.save(page=self) # Save triggers an email
40+
# Add a message to be displayed to the user
41+
messages.add_message(
42+
request, messages.INFO,
43+
self.get_success_message())
44+
# Redirect to the current page, to prevent resubmissions
45+
return HttpResponseRedirect(self.get_success_url())
46+
47+
return super().serve(request, *args, **kwargs)
48+
49+
def get_context(self, request):
50+
ctx = super().get_context(request)
51+
ctx['form'] = self.form
52+
return ctx
53+
54+
def get_success_url(self):
55+
return self.success_url or self.url
56+
57+
def get_success_message(self):
58+
return self.success_message
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% load wagtailcontact_tags %}
2+
3+
{% autoescape off %}
4+
Enquiry from {{ name }} ({{ email }}):
5+
{% endautoescape %}
6+
7+
{{ message|bleachclean }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ subject_prefix }}Enquiry from {{ name }}

‎wagtailcontact/templatetags/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django import template
2+
3+
import bleach
4+
5+
register = template.Library()
6+
7+
8+
@register.filter
9+
def bleachclean(value):
10+
return bleach.clean(value, strip=True)

0 commit comments

Comments
 (0)