From 7449f68522f6da9b9936dfcfa39474ddf020ad2e Mon Sep 17 00:00:00 2001 From: jsa Date: Tue, 14 Jul 2015 11:28:59 -0400 Subject: [PATCH] Support setting email opt-in in calls to the Otto shim XCOM-499 --- .../commerce/api/v0/tests/test_views.py | 35 ++++++++++++++++--- lms/djangoapps/commerce/api/v0/views.py | 27 ++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index 0c91875546..a506333b5d 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -1,7 +1,7 @@ """ Commerce API v0 view tests. """ import json +import itertools from uuid import uuid4 -from nose.plugins.attrib import attr import ddt from django.conf import settings @@ -9,6 +9,7 @@ from django.core.urlresolvers import reverse from django.test import TestCase from django.test.utils import override_settings import mock +from nose.plugins.attrib import attr from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -33,7 +34,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) """ Tests for the commerce orders view. """ - def _post_to_view(self, course_id=None): + def _post_to_view(self, course_id=None, marketing_email_opt_in=False): """ POST to the view being tested. @@ -42,8 +43,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) :return: Response """ - course_id = unicode(course_id or self.course.id) - return self.client.post(self.url, {'course_id': course_id}) + payload = { + "course_id": unicode(course_id or self.course.id) + } + if marketing_email_opt_in: + payload["email_opt_in"] = True + return self.client.post(self.url, payload) def assertResponseMessage(self, response, expected_msg): """ Asserts the detail field in the response's JSON body equals the expected message. """ @@ -297,6 +302,28 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) with mock_create_basket(): self._test_successful_ecommerce_api_call(False) + @mock.patch('commerce.api.v0.views.update_email_opt_in') + @ddt.data(*itertools.product((False, True), (False, True), (False, True))) + @ddt.unpack + def test_marketing_email_opt_in(self, is_opt_in, has_sku, is_exception, mock_update): + """ + Ensures the email opt-in flag is handled, if present, and that problems handling the + flag don't cause the rest of the enrollment transaction to fail. + """ + if not has_sku: + for course_mode in CourseMode.objects.filter(course_id=self.course.id): + course_mode.sku = None + course_mode.save() + + if is_exception: + mock_update.side_effect = Exception("boink") + + return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}} + with mock_create_basket(response=return_value, expect_called=has_sku): + response = self._post_to_view(marketing_email_opt_in=is_opt_in) + self.assertEqual(mock_update.called, is_opt_in) + self.assertEqual(response.status_code, 200) + @attr('shard_1') @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index 220263f8c7..617bdcdced 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -19,6 +19,7 @@ from courseware import courses from embargo import api as embargo_api from enrollment.api import add_enrollment from enrollment.views import EnrollmentCrossDomainSessionAuth +from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser from student.models import CourseEnrollment from util.json_request import JsonResponse @@ -62,6 +63,22 @@ class BasketsView(APIView): """ Enroll the user in the course. """ add_enrollment(user.username, unicode(course_key)) + def _handle_marketing_opt_in(self, request, course_key, user): + """ + Handle the marketing email opt-in flag, if it was set. + + Errors here aren't expected, but should not break the outer enrollment transaction. + """ + email_opt_in = request.DATA.get('email_opt_in', None) + if email_opt_in is not None: + try: + update_email_opt_in(user, course_key.org, email_opt_in) + except Exception: # pylint: disable=broad-except + # log the error, return silently + log.exception( + 'Failed to handle marketing opt-in flag: user="%s", course="%s"', user.username, course_key + ) + def post(self, request, *args, **kwargs): # pylint: disable=unused-argument """ Attempt to create the basket and enroll the user. @@ -96,6 +113,7 @@ class BasketsView(APIView): username=user.username) log.debug(msg) self._enroll(course_key, user) + self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) # Setup the API @@ -108,6 +126,8 @@ class BasketsView(APIView): log.debug(msg) return DetailResponse(msg) + response = None + # Make the API call try: response_data = api.baskets.post({ @@ -118,12 +138,12 @@ class BasketsView(APIView): payment_data = response_data["payment_data"] if payment_data: # Pass data to the client to begin the payment flow. - return JsonResponse(payment_data) + response = JsonResponse(payment_data) elif response_data['order']: # The order was completed immediately because there is no charge. msg = Messages.ORDER_COMPLETED.format(order_number=response_data['order']['number']) log.debug(msg) - return DetailResponse(msg) + response = DetailResponse(msg) else: msg = u'Unexpected response from basket endpoint.' log.error( @@ -143,6 +163,9 @@ class BasketsView(APIView): user_id=user.id ) + self._handle_marketing_opt_in(request, course_key, user) + return response + class BasketOrderView(APIView): """ Retrieve the order associated with a basket. """