diff --git a/bulbs/__init__.py b/bulbs/__init__.py index ef6497d0..3d67cd6b 100644 --- a/bulbs/__init__.py +++ b/bulbs/__init__.py @@ -1 +1 @@ -__version__ = "2.3.2" +__version__ = "2.4.0" diff --git a/bulbs/api/templates/__bug_report_email.html b/bulbs/api/templates/__bug_report_email.html new file mode 100644 index 00000000..6652c28b --- /dev/null +++ b/bulbs/api/templates/__bug_report_email.html @@ -0,0 +1,8 @@ +{% autoescape off %} + {% if report %} + {{ report }} + {% endif %} + URL: {{ url }} + User Agent: {{ user_agent }} + Submitted by: {{ submitted_by }} +{% endautoescape %} diff --git a/bulbs/api/urls.py b/bulbs/api/urls.py index e8696978..f97d5c84 100644 --- a/bulbs/api/urls.py +++ b/bulbs/api/urls.py @@ -2,10 +2,11 @@ from django.conf.urls import url, include from bulbs.cms_notifications.api import notifications_view -from .views import api_v1_router, MeViewSet +from .views import api_v1_router, MeViewSet, ReportBugEmail urlpatterns = ( + url(r"^report-bug/?$", ReportBugEmail.as_view(), name="report-bug"), url(r"^me/logout/?$", "django.contrib.auth.views.logout", name="logout"), url(r"^me/?$", MeViewSet.as_view({"get": "retrieve"}), name="me"), url(r"^", include(api_v1_router.urls)) # noqa diff --git a/bulbs/api/views.py b/bulbs/api/views.py index b8fe146c..6c4de30f 100644 --- a/bulbs/api/views.py +++ b/bulbs/api/views.py @@ -8,14 +8,16 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.core.urlresolvers import resolve, Resolver404 +from django.core.mail import EmailMessage from django.db.models.loading import get_models from django.http import Http404 from django.shortcuts import get_object_or_404 +from django.template import loader from django.utils import timezone from django.utils.dateparse import parse_datetime from djes.apps import indexable_registry -import elasticsearch + from elasticsearch_dsl.query import Q from elasticsearch_dsl import filter as es_filter from firebase_token_generator import create_token @@ -603,17 +605,50 @@ def create(self, request, *args, **kwargs): return Response(status=status.HTTP_200_OK) +class ReportBugEmail(APIView): + + def post(self, request): + SETTINGS = getattr(settings, "BUG_REPORTER", {}) + + report = request.DATA.get("report", "") + url = request.DATA.get("url", "") + user_agent = request.DATA.get("user_agent", "") + + if request.user.first_name and request.user.last_name: + name = request.user.get_full_name() + else: + name = request.user.get_username() + + mail = EmailMessage( + subject=SETTINGS.get("EMAIL_SUBJECT", "Hey! A bug was reported!"), + body=loader.render_to_string( + SETTINGS.get("EMAIL_TEMPLATE_PATH", "__bug_report_email.html"), + { + "report": report, + "url": url, + "user_agent": user_agent, + "submitted_by": name + } + ), + from_email=request.user.email, + to=SETTINGS.get("EMAIL_TO_ADDRESSES", ["webtech@theonion.com"]) + ) + mail.send() + + return Response({"message": "Message Sent!"}, status=status.HTTP_200_OK) + + # api router for aforementioned/defined viewsets # note: me view is registered in urls.py api_v1_router = routers.DefaultRouter() +api_v1_router.register(r"author", AuthorViewSet, base_name="author") api_v1_router.register(r"content", ContentViewSet, base_name="content") -api_v1_router.register(r"custom-search-content", CustomSearchContentViewSet, base_name="custom-search-content") -api_v1_router.register(r"content-type", ContentTypeViewSet, base_name="content-type") api_v1_router.register(r"content-resolve", ContentResolveViewSet, base_name="content-resolve") +api_v1_router.register(r"content-type", ContentTypeViewSet, base_name="content-type") +api_v1_router.register(r"contributor-email", SendContributorReport, base_name="contributor-email") +api_v1_router.register(r"custom-search-content", CustomSearchContentViewSet, base_name="custom-search-content") +api_v1_router.register(r"feature-type", FeatureTypeViewSet, base_name="feature-type") +api_v1_router.register(r"log", LogEntryViewSet, base_name="logentry") api_v1_router.register(r"special-coverage-resolve", SpecialCoverageResolveViewSet, base_name="special-coverage-resolve") api_v1_router.register(r"tag", TagViewSet, base_name="tag") -api_v1_router.register(r"log", LogEntryViewSet, base_name="logentry") -api_v1_router.register(r"author", AuthorViewSet, base_name="author") -api_v1_router.register(r"feature-type", FeatureTypeViewSet, base_name="feature-type") api_v1_router.register(r"user", UserViewSet, base_name="user") -api_v1_router.register(r"contributor-email", SendContributorReport, base_name="contributor-email") diff --git a/scripts/test b/scripts/test index f2bc3e71..a0fa35a3 100755 --- a/scripts/test +++ b/scripts/test @@ -1,5 +1,4 @@ #!/bin/sh -# Run all tests -# +# Summary: Run all test. py.test "$@" diff --git a/tests/api/test_bug_report_email.py b/tests/api/test_bug_report_email.py new file mode 100644 index 00000000..cafa79af --- /dev/null +++ b/tests/api/test_bug_report_email.py @@ -0,0 +1,121 @@ +from django.contrib.auth import get_user_model +from django.core import mail +from django.core.urlresolvers import reverse +from django.test.utils import override_settings + +from rest_framework.status import HTTP_200_OK +from rest_framework.test import APIClient + +from bulbs.utils.test import BaseAPITestCase + +User = get_user_model() + +TEST_EMAIL_TEMPLATE_PATH = "__bug_report_email.html" +TEST_EMAIL_TO_ADDRESSES = [ + "webtech@theonion.com", + "bugs@theonion.net" +] +TEST_EMAIL_SUBJECT = "Hello from the CMS!" + + +@override_settings( + EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", + BUG_REPORTER={ + "EMAIL_TEMPLATE_PATH": TEST_EMAIL_TEMPLATE_PATH, + "EMAIL_TO_ADDRESSES": TEST_EMAIL_TO_ADDRESSES, + "EMAIL_SUBJECT": TEST_EMAIL_SUBJECT + } +) +class TestBugReportEmail(BaseAPITestCase): + + def post_mail(self, data=None): + return self.client.post( + reverse("report-bug"), + data=data, + format="json" + ) + + def create_mail(self, data=None): + if data is None: + data = self.test_data + + self.post_mail(data) + + return mail.outbox[0] + + def setUp(self): + super(TestBugReportEmail, self).setUp() + + self.test_data = { + "report": "My garbage report", + "url": "www.theonion.com/my/garbage/article", + "user_agent": "Firebox 2.0", + } + + self.test_user = User.objects.create_user( + "jbiden", + "diamond.joe@comcast.net", + "dj" + ) + + self.client = APIClient() + self.client.login(username=self.test_user.username, password="dj") + + def test_email_body(self): + """Test that an email is sent with all the info in the body.""" + + email = self.create_mail() + + self.assertIn(self.test_data["report"], email.body) + self.assertIn(self.test_data["url"], email.body) + self.assertIn(self.test_data["user_agent"], email.body) + + def test_email_to_addresses(self): + """Test that email is sent with the to addresses specified by settings.""" + + email = self.create_mail() + + self.assertEqual(TEST_EMAIL_TO_ADDRESSES[0], email.to[0]) + self.assertEqual(TEST_EMAIL_TO_ADDRESSES[1], email.to[1]) + + def test_email_from_address(self): + """Test that the email is sent with the current user's email address.""" + + email = self.create_mail() + + self.assertEqual(self.test_user.email, email.from_email) + + def test_email_subject(self): + """Test that email is sent with the subject specified by settings.""" + + email = self.create_mail() + + self.assertEqual(TEST_EMAIL_SUBJECT, email.subject) + + def test_name_last_and_first(self): + """Test that user's full name is used if they have a first and last name + set.""" + + self.test_user.first_name = "Joe" + self.test_user.last_name = "Biden" + self.test_user.save() + + email = self.create_mail() + + self.assertIn(self.test_user.first_name, email.body) + self.assertIn(self.test_user.last_name, email.body) + + def test_name_no_last_and_first(self): + """Test that user's user name is used if they don't have a first and + last name.""" + + email = self.create_mail() + + self.assertIn(self.test_user.username, email.body) + + def test_http_response_200(self): + """Test response that's sent back to client on a 200.""" + + response = self.post_mail() + + self.assertEqual(response.status_code, HTTP_200_OK)