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