Skip to content

Commit

Permalink
Add email processing view for meico form (mozilla#12497)
Browse files Browse the repository at this point in the history
* Add email processing view for meico form

* Add custom CORS headers (permissive for now)

* Add form for validation of data

* Update CORS origin
  • Loading branch information
robhudson authored Dec 20, 2022
1 parent 922fb73 commit c84e267
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 3 deletions.
12 changes: 12 additions & 0 deletions bedrock/mozorg/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datetime import datetime
from random import randrange

from django import forms
from django.forms import widgets
from django.urls import reverse
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -75,3 +76,14 @@ class TelInput(widgets.TextInput):

class NumberInput(widgets.TextInput):
input_type = "number"


class MeicoEmailForm(forms.Form):
"""
A form class used to validate the data coming from the MEICO site.
"""

name = forms.CharField(required=False, max_length=100)
email = forms.EmailField()
interests = forms.CharField(required=False, max_length=100)
description = forms.CharField(required=False, max_length=750)
9 changes: 9 additions & 0 deletions bedrock/mozorg/templates/mozorg/emails/meico-email.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The following was submitted on the MEICO form:

Name: {{ data.name }}
E-mail: {{ data.email }}

Interests: {{ data.interests }}

Message:
{{ data.description }}
63 changes: 63 additions & 0 deletions bedrock/mozorg/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import json
import os
from unittest.mock import ANY, Mock, patch

from django.core import mail
from django.http.response import HttpResponse
from django.test.client import RequestFactory

Expand Down Expand Up @@ -222,3 +224,64 @@ def test_redirect(self):
resp = self.client.get("/webvision/", follow=True, HTTP_ACCEPT_LANGUAGE="en")
self.assertEqual(resp.redirect_chain[0], ("/about/webvision/", 301))
self.assertEqual(resp.redirect_chain[1], ("/en-US/about/webvision/", 302))


class TestMeicoEmail(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.view = views.meico_email_form
with self.activate("en-US"):
self.url = reverse("mozorg.email_meico")

self.data = {
"name": "The Dude",
"email": "foo@bar.com",
"interests": "abiding, bowling",
"description": "The rug really tied the room together.",
}

def tearDown(self):
mail.outbox = []

def test_not_post(self):
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.content, b'{"error": 400, "message": "Only POST requests are allowed"}')
self.assertIn("Access-Control-Allow-Origin", resp.headers)
self.assertIn("Access-Control-Allow-Headers", resp.headers)

def test_bad_json(self):
resp = self.client.post(self.url, content_type="application/json", data='{{"bad": "json"}')
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.content, b'{"error": 400, "message": "Error decoding JSON"}')
self.assertIn("Access-Control-Allow-Origin", resp.headers)
self.assertIn("Access-Control-Allow-Headers", resp.headers)

def test_invalid_email(self):
resp = self.client.post(self.url, content_type="application/json", data='{"email": "foo@bar"}')
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.content, b'{"error": 400, "message": "Invalid form data"}')
self.assertIn("Access-Control-Allow-Origin", resp.headers)
self.assertIn("Access-Control-Allow-Headers", resp.headers)

@patch("bedrock.mozorg.views.render_to_string", return_value="rendered")
@patch("bedrock.mozorg.views.EmailMessage")
def test_success(self, mock_emailMessage, mock_render_to_string):
resp = self.client.post(self.url, content_type="application/json", data=json.dumps(self.data))

self.assertEqual(resp.status_code, 200)
mock_emailMessage.assert_called_once_with(views.MEICO_EMAIL_SUBJECT, "rendered", views.MEICO_EMAIL_SENDER, views.MEICO_EMAIL_TO)
self.assertEqual(resp.content, b'{"status": "ok"}')
self.assertIn("Access-Control-Allow-Origin", resp.headers)
self.assertIn("Access-Control-Allow-Headers", resp.headers)

def test_outbox(self):
resp = self.client.post(self.url, content_type="application/json", data=json.dumps(self.data))
self.assertEqual(resp.status_code, 200)
self.assertEqual(len(mail.outbox), 1)

email = mail.outbox[0]
self.assertIn(f"Name: {self.data['name']}", email.body)
self.assertIn(f"E-mail: {self.data['email']}", email.body)
self.assertIn(f"Interests: {self.data['interests']}", email.body)
self.assertIn(f"Message:\n{self.data['description']}", email.body)
1 change: 1 addition & 0 deletions bedrock/mozorg/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
name="mozorg.about.webvision.full",
),
page("analytics-tests/", "mozorg/analytics-tests/ga-index.html"),
path("email-meico/", views.meico_email_form, name="mozorg.email_meico"),
)

if settings.DEV:
Expand Down
57 changes: 57 additions & 0 deletions bedrock/mozorg/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import json

from django.conf import settings
from django.core.mail import EmailMessage
from django.http import Http404
from django.shortcuts import render as django_render
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.http import require_safe
from django.views.generic import TemplateView

from commonware.decorators import xframe_allow
from jsonview.decorators import json_view
from product_details import product_details
from sentry_sdk import capture_exception

Expand All @@ -20,6 +25,7 @@
from bedrock.contentful.api import ContentfulPage
from bedrock.contentful.models import ContentfulEntry
from bedrock.mozorg.credits import CreditsFile
from bedrock.mozorg.forms import MeicoEmailForm
from bedrock.mozorg.models import WebvisionDoc
from bedrock.pocketfeed.models import PocketArticle
from lib import l10n_utils
Expand Down Expand Up @@ -231,3 +237,54 @@ def get_context_data(self, **kwargs):
def as_view(cls, **initkwargs):
cache_timeout = initkwargs.pop("cache_timeout", cls.cache_timeout)
return cache_page(cache_timeout)(super().as_view(**initkwargs))


MEICO_EMAIL_SUBJECT = "MEICO Interest Form"
MEICO_EMAIL_SENDER = "Mozilla.com <noreply@mozilla.com>"
MEICO_EMAIL_TO = ["meico@mozilla.com"]


@json_view
def meico_email_form(request):
"""
This form accepts a POST request from future.mozilla.org/meico and will send
an email with the data included in the email.
"""
CORS_HEADERS = {
"Access-Control-Allow-Origin": "https://future.mozilla.org",
"Access-Control-Allow-Headers": "accept, accept-encoding, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with",
}

if request.method == "OPTIONS":
return {}, 200, CORS_HEADERS

if request.method != "POST":
return {"error": 400, "message": "Only POST requests are allowed"}, 400, CORS_HEADERS

try:
json_data = json.loads(request.body.decode("utf-8"))
except json.decoder.JSONDecodeError:
return {"error": 400, "message": "Error decoding JSON"}, 400, CORS_HEADERS

form = MeicoEmailForm(
{
"email": json_data.get("email", ""),
"name": json_data.get("name", ""),
"interests": json_data.get("interests", ""),
"description": json_data.get("description", ""),
}
)

if not form.is_valid():
return {"error": 400, "message": "Invalid form data"}, 400, CORS_HEADERS

email_msg = render_to_string("mozorg/emails/meico-email.txt", {"data": form.cleaned_data}, request=request)

email = EmailMessage(MEICO_EMAIL_SUBJECT, email_msg, MEICO_EMAIL_SENDER, MEICO_EMAIL_TO)

try:
email.send()
except Exception as e:
return {"error": 400, "message": str(e)}, 400, CORS_HEADERS

return {"status": "ok"}, 200, CORS_HEADERS
6 changes: 3 additions & 3 deletions bedrock/press/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def test_form_honeypot(self):

assert not form.is_valid()

def test_form_valid_attachement(self):
def test_form_valid_attachment(self):
"""
Form should be valid when attachment under/at size limit.
"""
Expand All @@ -258,7 +258,7 @@ def test_form_valid_attachement(self):
# make sure form is valid
assert form.is_valid()

def test_form_invalid_attachement(self):
def test_form_invalid_attachment(self):
"""
Form should be invalid and contain attachment errors when attachment
over size limit.
Expand Down Expand Up @@ -301,7 +301,7 @@ def test_email(self, mock_email_message, mock_render_to_string):

@patch("bedrock.press.views.render_to_string", return_value="rendered")
@patch("bedrock.press.views.EmailMessage")
def test_email_with_attachement(self, mock_email_message, mock_render_to_string):
def test_email_with_attachment(self, mock_email_message, mock_render_to_string):
"""
Make sure email is sent with attachment.
"""
Expand Down

0 comments on commit c84e267

Please sign in to comment.