From 07818d3cc4016780bd2bd7b76d4d5753187bb198 Mon Sep 17 00:00:00 2001 From: Todd Dembrey <todd.dembrey@torchbox.com> Date: Thu, 9 Aug 2018 11:21:20 +0100 Subject: [PATCH] Create the mailchimp newsletter form --- opentech/public/mailchimp/__init__.py | 0 opentech/public/mailchimp/apps.py | 5 ++ opentech/public/mailchimp/forms.py | 15 +++++ .../public/mailchimp/migrations/__init__.py | 0 opentech/public/mailchimp/models.py | 3 + .../mailchimp/newsletter_signup.html | 16 +++++ opentech/public/mailchimp/tests.py | 60 +++++++++++++++++ opentech/public/mailchimp/urls.py | 11 ++++ opentech/public/mailchimp/views.py | 66 +++++++++++++++++++ opentech/public/urls.py | 5 +- opentech/public/utils/context_processors.py | 3 + opentech/settings/base.py | 5 ++ .../src/sass/public/components/_form.scss | 2 +- .../src/sass/public/components/_messages.scss | 44 +++++++++++++ .../src/sass/public/layout/_footer.scss | 22 ++++--- opentech/static_src/src/sass/public/main.scss | 2 +- opentech/templates/base.html | 29 +------- requirements.txt | 3 +- 18 files changed, 249 insertions(+), 42 deletions(-) create mode 100644 opentech/public/mailchimp/__init__.py create mode 100644 opentech/public/mailchimp/apps.py create mode 100644 opentech/public/mailchimp/forms.py create mode 100644 opentech/public/mailchimp/migrations/__init__.py create mode 100644 opentech/public/mailchimp/models.py create mode 100644 opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html create mode 100644 opentech/public/mailchimp/tests.py create mode 100644 opentech/public/mailchimp/urls.py create mode 100644 opentech/public/mailchimp/views.py create mode 100644 opentech/static_src/src/sass/public/components/_messages.scss diff --git a/opentech/public/mailchimp/__init__.py b/opentech/public/mailchimp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opentech/public/mailchimp/apps.py b/opentech/public/mailchimp/apps.py new file mode 100644 index 000000000..6825eca97 --- /dev/null +++ b/opentech/public/mailchimp/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MailchimpConfig(AppConfig): + name = 'mailchimp' diff --git a/opentech/public/mailchimp/forms.py b/opentech/public/mailchimp/forms.py new file mode 100644 index 000000000..afda5b9e3 --- /dev/null +++ b/opentech/public/mailchimp/forms.py @@ -0,0 +1,15 @@ +from django import forms + + +class NewsletterForm(forms.Form): + email = forms.EmailField(label='Email Address') + fname = forms.CharField(label='First Name', required=False) + lname = forms.CharField(label='Last Name', required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + class_name = 'input--secondary' + if field.required: + class_name += ' input__secondary--required' + field.widget.attrs = {'class': class_name} diff --git a/opentech/public/mailchimp/migrations/__init__.py b/opentech/public/mailchimp/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opentech/public/mailchimp/models.py b/opentech/public/mailchimp/models.py new file mode 100644 index 000000000..71a836239 --- /dev/null +++ b/opentech/public/mailchimp/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html new file mode 100644 index 000000000..4c70ae9ab --- /dev/null +++ b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html @@ -0,0 +1,16 @@ +<h4>Get the latest internet freedom news</h4> +<form class="form" action="{% url "newsletter:subscribe" %}" method="post"> + <div> + {% for field in newsletter_form %} + <label for="{{ field.id_for_label }}"{% if field.field.required %} required{% endif %}> + <span>{{ field.label }}</span> + {% if field.field.required %} + <span class="form__required">*</span> + {% endif %} + {{ field }} + {% endfor %} + <div class="form-actions form-wrapper"> + <input type="submit" value="Sign up" class="form-submit link link--button-transparent link--footer-signup"> + </div> + </div> +</form> diff --git a/opentech/public/mailchimp/tests.py b/opentech/public/mailchimp/tests.py new file mode 100644 index 000000000..660e53682 --- /dev/null +++ b/opentech/public/mailchimp/tests.py @@ -0,0 +1,60 @@ +from unittest import mock +import re + +from django.test import override_settings, TestCase +from django.urls import reverse + +import responses + + +class TestNewsletterView(TestCase): + url = reverse('newsletter:subscribe') + + def setUp(self): + self.origin = 'https://testserver/' + self.client.defaults = {'HTTP_ORIGIN': self.origin} + + def test_redirected_home_if_get(self): + response = self.client.get(self.url, secure=True, follow=True) + request = response.request + self.assertRedirects(response, '{}://{}/'.format(request['wsgi.url_scheme'], request['SERVER_NAME'])) + + def test_can_subscribe(self): + response = self.client.post(self.url, data={'email': 'email@email.com'}, secure=True, follow=True) + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('Thank you', str(messages[0])) + + def test_error_in_form(self): + response = self.client.post(self.url, data={'email': 'email_is_bad.com'}, secure=True, follow=True) + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('errors with', str(messages[0])) + + @override_settings( + MAILCHIMP_API_KEY='a' * 32, + MAILCHIMP_LIST_ID='12345' + ) + @responses.activate + @mock.patch('opentech.public.mailchimp.views.logging') + def test_error_with_mailchimp(self, logging): + any_url = re.compile(".") + # Copied from the mailchimp playground + response_data = { + "title": "Invalid Resource", + "status": 400, + "detail": "Please provide a valid email address.", + } + responses.add(responses.POST, any_url, json=response_data, status=400) + response = self.client.post(self.url, data={'email': 'email@email.com'}, secure=True, follow=True) + + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('problem', str(messages[0])) + logging.info.assert_called_once_with(response_data) diff --git a/opentech/public/mailchimp/urls.py b/opentech/public/mailchimp/urls.py new file mode 100644 index 000000000..4fe8fd700 --- /dev/null +++ b/opentech/public/mailchimp/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from .views import MailchimpSubscribeView + + +app_name = 'newsletter' + + +urlpatterns = [ + path('subscribe/', MailchimpSubscribeView.as_view(), name='subscribe') +] diff --git a/opentech/public/mailchimp/views.py b/opentech/public/mailchimp/views.py new file mode 100644 index 000000000..e06f1263d --- /dev/null +++ b/opentech/public/mailchimp/views.py @@ -0,0 +1,66 @@ +import logging + +from django.conf import settings +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import RedirectView +from django.views.generic.edit import FormMixin + +from mailchimp3 import MailChimp + +from .forms import NewsletterForm + +logging.getLogger('opentech') + + +@method_decorator(csrf_exempt, name='dispatch') +class MailchimpSubscribeView(FormMixin, RedirectView): + form_class = NewsletterForm + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_invalid(self, form): + messages.error(self.request, _('Sorry, there were errors with your form.') + str(form.errors)) + return HttpResponseRedirect(self.get_success_url()) + + def form_valid(self, form): + mailchimp_enabled = settings.MAILCHIMP_API_KEY and settings.MAILCHIMP_LIST_ID + + dummy_key = 'a' * 32 + + client = MailChimp(mc_api=settings.MAILCHIMP_API_KEY or dummy_key, timeout=5.0, enabled=mailchimp_enabled) + + data = form.cleaned_data.copy() + email = data.pop('email') + data = { + k.upper(): v + for k, v in data.items() + } + try: + client.lists.members.create(settings.MAILCHIMP_LIST_ID, { + 'email_address': email, + 'status': 'pending', + 'merge_fields': data, + }) + except Exception as e: + messages.warning(self.request, _('Sorry, there has been an problem. Please try again later.')) + logging.info(e.args[0]) + else: + messages.success(self.request, _('Thank you for subscribing')) + return super().form_valid(form) + + def get_success_url(self): + # Go back to where you came from + return self.request.META['HTTP_ORIGIN'] + + def get_redirect_url(self): + # We don't know where you came from, go home + return '/' diff --git a/opentech/public/urls.py b/opentech/public/urls.py index 10ac60a78..44aef2a3e 100644 --- a/opentech/public/urls.py +++ b/opentech/public/urls.py @@ -1,9 +1,12 @@ -from django.urls import path +from django.urls import include, path from .esi import views as esi_views from .search import views as search_views +from .mailchimp import urls as newsletter_urls + urlpatterns = [ path('esi/<slug>/', esi_views.esi, name='esi'), path('search/', search_views.search, name='search'), + path('newsletter/', include(newsletter_urls)) ] diff --git a/opentech/public/utils/context_processors.py b/opentech/public/utils/context_processors.py index 2444fdb1b..149a40181 100644 --- a/opentech/public/utils/context_processors.py +++ b/opentech/public/utils/context_processors.py @@ -1,9 +1,12 @@ from opentech.apply.home.models import ApplyHomePage from opentech.public.home.models import HomePage +from opentech.public.mailchimp.forms import NewsletterForm + def global_vars(request): return { 'APPLY_SITE': ApplyHomePage.objects.first().get_site(), 'PUBLIC_SITE': HomePage.objects.first().get_site(), + 'newsletter_form': NewsletterForm() } diff --git a/opentech/settings/base.py b/opentech/settings/base.py index 4171f5bcf..ff727923e 100644 --- a/opentech/settings/base.py +++ b/opentech/settings/base.py @@ -29,6 +29,7 @@ INSTALLED_APPS = [ 'opentech.public.esi', 'opentech.public.funds', 'opentech.public.home', + 'opentech.public.mailchimp', 'opentech.public.navigation', 'opentech.public.news', 'opentech.public.people', @@ -371,3 +372,7 @@ if 'REDIS_URL' in env: CELERY_BROKER_URL = env.get('REDIS_URL') else: CELERY_TASK_ALWAYS_EAGER = True + + +MAILCHIMP_API_KEY = env.get('MAILCHIMP_API_KEY') +MAILCHIMP_LIST_ID = env.get('MAILCHIMP_LIST_ID') diff --git a/opentech/static_src/src/sass/public/components/_form.scss b/opentech/static_src/src/sass/public/components/_form.scss index 1d8166740..d8d2e59d3 100644 --- a/opentech/static_src/src/sass/public/components/_form.scss +++ b/opentech/static_src/src/sass/public/components/_form.scss @@ -155,7 +155,7 @@ input[type='text']:not(.input--secondary), input[type='date'], input[type='time'], - input[type='email'], + input[type='email']:not(.input--secondary), input[type='number'], input[type='password'], input[type='datetime-local'] { diff --git a/opentech/static_src/src/sass/public/components/_messages.scss b/opentech/static_src/src/sass/public/components/_messages.scss new file mode 100644 index 000000000..8df0d9785 --- /dev/null +++ b/opentech/static_src/src/sass/public/components/_messages.scss @@ -0,0 +1,44 @@ +.messages { + position: relative; + right: 50%; + left: 50%; + width: 100vw; + margin-right: -50vw; + margin-left: -50vw; + + &__text { + position: relative; + max-height: 1000px; + padding: 15px; + padding-right: 35px; + border: solid 1px; + + &--info , &--success { + background: $color--mint; + border-color: darken($color--mint, 20%); + } + + &--warning { + font-weight: bold; + color: $color--white; + background: $color--error; + border-color: darken($color--error, 20%); + } + + &--hide { + max-height: 0; + padding-top: 0; + padding-bottom: 0; + border: 0 none; + transition: all $transition; // sass-lint:disable-line no-transition-all + transform-origin: top; + } + } + + &__close { + position: absolute; + top: 15px; + right: 15px; + + } +} diff --git a/opentech/static_src/src/sass/public/layout/_footer.scss b/opentech/static_src/src/sass/public/layout/_footer.scss index a1f5d97e3..ac71e029c 100644 --- a/opentech/static_src/src/sass/public/layout/_footer.scss +++ b/opentech/static_src/src/sass/public/layout/_footer.scss @@ -45,16 +45,18 @@ } } - input[type='text'] { - width: 100%; - max-width: 390px; - margin-bottom: 1rem; - color: $color--white; - background: transparent; - border-top: 0; - border-right: 0; - border-bottom: 4px solid $color--light-blue; - border-left: 0; + input{ + &[type='text'], &[type='email'] { + width: 100%; + max-width: 390px; + margin-bottom: 1rem; + color: $color--white; + background: transparent; + border-top: 0; + border-right: 0; + border-bottom: 4px solid $color--light-blue; + border-left: 0; + } } label { diff --git a/opentech/static_src/src/sass/public/main.scss b/opentech/static_src/src/sass/public/main.scss index de867a4a6..8928471a2 100755 --- a/opentech/static_src/src/sass/public/main.scss +++ b/opentech/static_src/src/sass/public/main.scss @@ -28,6 +28,7 @@ @import 'components/list'; @import 'components/listing'; @import 'components/media-box'; +@import 'components/messages'; @import 'components/nav'; @import 'components/responsive-object'; @import 'components/rich-text'; @@ -40,4 +41,3 @@ @import 'layout/sidebar'; // Pages - diff --git a/opentech/templates/base.html b/opentech/templates/base.html index aec44c328..f62787833 100644 --- a/opentech/templates/base.html +++ b/opentech/templates/base.html @@ -153,34 +153,7 @@ <footer class="footer"> <div class="grid grid--two wrapper wrapper--large"> <div class="footer__inner"> - <h4>Get the latest internet freedom news</h4> - <form class="form"> - <div> - <div class="mailchimp-signup-subscribe-form-description"></div> - <div id="mailchimp-newsletter-32632431e3-mergefields" class="mailchimp-newsletter-mergefields"> - <div class="form-item form-type-textfield form-item-mergevars-EMAIL"> - <label for="edit-mergevars-email">Email Address <span class="form-required" title="This field is required.">*</span></label> - <input type="text" id="edit-mergevars-email" name="mergevars[EMAIL]" value="" size="25" maxlength="128" class="form-text required input--secondary"> - </div> - - <div class="form-item form-type-textfield form-item-mergevars-FNAME"> - <label for="edit-mergevars-fname">First Name </label> - <input type="text" id="edit-mergevars-fname" name="mergevars[FNAME]" value="" size="25" maxlength="128" class="form-text input--secondary"> - </div> - - <div class="form-item form-type-textfield form-item-mergevars-LNAME"> - <label for="edit-mergevars-lname">Last Name </label> - <input type="text" id="edit-mergevars-lname" name="mergevars[LNAME]" value="" size="25" maxlength="128" class="form-text input--secondary"> - </div> - - </div> - <input type="hidden" name="form_build_id" value="form-2Dy9x5istHLUmufjcHabtyuZ_niL-RlfSoHBIq39hpI"> - <input type="hidden" name="form_id" value="mailchimp_signup_subscribe_block_otf_newsletter_form"> - <div class="form-actions form-wrapper" id="edit-actions--3"> - <input type="submit" id="edit-submit--3" name="op" value="Sign up" class="form-submit link link--button-transparent link--footer-signup"> - </div> - </div> - </form> + {% include "mailchimp/newsletter_signup.html" %} </div> <div class="footer__inner"> diff --git a/requirements.txt b/requirements.txt index 0ea9b0b33..4a5561150 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ celery==4.2.1 factory_boy==2.9.2 # wagtail_factories - waiting on merge and release form master branch git+git://github.com/mvantellingen/wagtail-factories.git#egg=wagtail_factories -responses == 0.9.0 +responses==0.9.0 flake8 @@ -33,3 +33,4 @@ whitenoise==2.0.4 uwsgi==2.0.15 ConcurrentLogHandler==0.9.1 raven==6.9.0 +mailchimp3==3.0.4 -- GitLab