From 893b7a00605478c50b2a4dc17262f9e03ec5d798 Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Mon, 14 Aug 2017 15:13:26 -0400 Subject: [PATCH] Stop audit and honor mode creating basket and order LEARNER-2222 --- .../commerce/api/v0/tests/test_views.py | 26 ++++++++- lms/djangoapps/commerce/api/v0/views.py | 56 ++++++++++++------- lms/djangoapps/commerce/constants.py | 1 + lms/djangoapps/commerce/utils.py | 3 + .../core/djangoapps/waffle_utils/__init__.py | 2 +- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index ea0f924a4f..cb3d84bd93 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -15,7 +15,7 @@ from django.test.utils import override_settings from edx_rest_api_client import exceptions from nose.plugins.attrib import attr -from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE +from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE, STOP_BASKET_CREATION_FLAG from commerce.constants import Messages from commerce.tests import TEST_BASKET_ID, TEST_ORDER_NUMBER, TEST_PAYMENT_DATA from commerce.tests.mocks import mock_basket_order, mock_create_basket @@ -23,6 +23,7 @@ from commerce.tests.test_views import UserMixin from course_modes.models import CourseMode from enrollment.api import get_enrollment from openedx.core.djangoapps.embargo.test_utils import restrict_course +from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.lib.django_test_client_utils import get_absolute_url from student.models import CourseEnrollment from student.tests.factories import CourseModeFactory @@ -192,6 +193,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) cookie_name=UTM_COOKIE_NAME, cookie_contents=json.dumps(UTM_COOKIE_CONTENTS)) self.assertIn(cookie_string, httpretty.last_request().headers['cookie']) + @override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=False) @ddt.data(True, False) def test_course_with_honor_seat_sku(self, user_is_active): """ @@ -214,6 +216,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) # Test that call with cookie passes cookie along self._test_successful_ecommerce_api_call(utm_tracking_present=True) + @override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=False) @ddt.data(True, False) def test_course_with_paid_seat_sku(self, user_is_active): """ @@ -228,6 +231,27 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) with mock_create_basket(response=return_value): self._test_successful_ecommerce_api_call(is_completed=False) + @override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=True) + @ddt.data(True, False) + def test_course_without_creating_order(self, user_is_active): + """ + If the course has a SKU, and the STOP_BASKET_CREATION waffle flag is on, + the enrollment should happen without contacting ecommerce api + """ + # Set user's active flag + self.user.is_active = user_is_active + self.user.save() # pylint: disable=no-member + with mock_create_basket(expect_called=False): + response = self._post_to_view() + + # Validate the response content + self.assertEqual(response.status_code, 200) + msg = Messages.ENROLL_DIRECTLY.format( + course_id=self.course.id, + username=self.user.username + ) + self.assertResponseMessage(response, msg) + def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG): """ Validates the view bypasses the E-Commerce API when the course has no CourseModes with SKUs. diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index 80bb149548..d6ff0fd663 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -13,6 +13,7 @@ from rest_framework.views import APIView from commerce.constants import Messages from commerce.exceptions import InvalidResponseError from commerce.http import DetailResponse, InternalRequestErrorResponse +from commerce.utils import COMMERCE_API_WAFFLE_FLAG_NAMESPACE from course_modes.models import CourseMode from courseware import courses from enrollment.api import add_enrollment @@ -20,6 +21,7 @@ from enrollment.views import EnrollmentCrossDomainSessionAuth from openedx.core.djangoapps.commerce.utils import ecommerce_api_client from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in +from openedx.core.djangoapps.waffle_utils import WaffleFlag from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser from openedx.core.lib.log_utils import audit_log from student.models import CourseEnrollment, RegistrationCookieConfiguration @@ -27,6 +29,7 @@ from util.json_request import JsonResponse log = logging.getLogger(__name__) SAILTHRU_CAMPAIGN_COOKIE = 'sailthru_bid' +STOP_BASKET_CREATION_FLAG = WaffleFlag(COMMERCE_API_WAFFLE_FLAG_NAMESPACE, 'stop_basket_creation') class BasketsView(APIView): @@ -82,7 +85,7 @@ class BasketsView(APIView): def post(self, request, *args, **kwargs): """ - Attempt to create the basket and enroll the user. + Attempt to enroll the user, and if needed, create the basket. """ user = request.user valid, course_key, error = self._is_data_valid(request) @@ -121,26 +124,49 @@ class BasketsView(APIView): if not default_enrollment_mode: msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) - elif default_enrollment_mode and not default_enrollment_mode.sku: - # If there are no course modes with SKUs, enroll the user without contacting the external API. - msg = Messages.NO_SKU_ENROLLED.format( - enrollment_mode=default_enrollment_mode.slug, - course_id=course_id, - username=user.username + elif not default_enrollment_mode.sku or STOP_BASKET_CREATION_FLAG.is_enabled(): + msg = Messages.ENROLL_DIRECTLY.format( + username=user.username, + course_id=course_id ) + if not default_enrollment_mode.sku: + # If there are no course modes with SKUs, return a different message. + msg = Messages.NO_SKU_ENROLLED.format( + enrollment_mode=default_enrollment_mode.slug, + course_id=course_id, + username=user.username + ) log.info(msg) self._enroll(course_key, user, default_enrollment_mode.slug) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) + else: + return self._create_basket_to_order(request, user, course_key, default_enrollment_mode) + def _add_request_cookie_to_api_session(self, server_session, request, cookie_name): + """ Add cookie from user request into server session """ + user_cookie = None + if cookie_name: + user_cookie = request.COOKIES.get(cookie_name) + if user_cookie: + server_cookie = {cookie_name: user_cookie} + if server_session.cookies: + requests.utils.add_dict_to_cookiejar(server_session.cookies, server_cookie) + else: + server_session.cookies = requests.utils.cookiejar_from_dict(server_cookie) + + def _create_basket_to_order(self, request, user, course_key, default_enrollment_mode): + """ + Connect to the ecommerce service to create the basket and the order to do the enrollment + """ # Setup the API - + course_id = unicode(course_key) try: api_session = requests.Session() api = ecommerce_api_client(user, session=api_session) except ValueError: self._enroll(course_key, user) - msg = Messages.NO_ECOM_API.format(username=user.username, course_id=unicode(course_key)) + msg = Messages.NO_ECOM_API.format(username=user.username, course_id=course_id) log.debug(msg) return DetailResponse(msg) @@ -191,18 +217,6 @@ class BasketsView(APIView): self._handle_marketing_opt_in(request, course_key, user) return response - def _add_request_cookie_to_api_session(self, server_session, request, cookie_name): - """ Add cookie from user request into server session """ - user_cookie = None - if cookie_name: - user_cookie = request.COOKIES.get(cookie_name) - if user_cookie: - server_cookie = {cookie_name: user_cookie} - if server_session.cookies: - requests.utils.add_dict_to_cookiejar(server_session.cookies, server_cookie) - else: - server_session.cookies = requests.utils.cookiejar_from_dict(server_cookie) - class BasketOrderView(APIView): """ Retrieve the order associated with a basket. """ diff --git a/lms/djangoapps/commerce/constants.py b/lms/djangoapps/commerce/constants.py index 5b44b97fe9..8e3d847a02 100644 --- a/lms/djangoapps/commerce/constants.py +++ b/lms/djangoapps/commerce/constants.py @@ -12,6 +12,7 @@ class Messages(object): """ Strings used to populate response messages. """ NO_ECOM_API = u'E-Commerce API not setup. Enrolled {username} in {course_id} directly.' NO_SKU_ENROLLED = u'The {enrollment_mode} mode for {course_id} does not have a SKU. Enrolling {username} directly.' + ENROLL_DIRECTLY = u'Enroll {username} in {course_id} directly because no need for E-Commerce baskets and orders.' ORDER_COMPLETED = u'Order {order_number} was completed.' ORDER_INCOMPLETE_ENROLLED = u'Order {order_number} was created, but is not yet complete. User was enrolled.' NO_HONOR_MODE = u'Course {course_id} does not have an honor mode.' diff --git a/lms/djangoapps/commerce/utils.py b/lms/djangoapps/commerce/utils.py index 7de0e6d6f1..465de98697 100644 --- a/lms/djangoapps/commerce/utils.py +++ b/lms/djangoapps/commerce/utils.py @@ -7,6 +7,9 @@ from django.conf import settings from commerce.models import CommerceConfiguration from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace + +COMMERCE_API_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='commerce_api') def is_account_activation_requirement_disabled(): diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py index 41376a05d1..30a8460e8b 100644 --- a/openedx/core/djangoapps/waffle_utils/__init__.py +++ b/openedx/core/djangoapps/waffle_utils/__init__.py @@ -208,7 +208,7 @@ class WaffleFlagNamespace(WaffleNamespace): """ # validate arguments namespaced_flag_name = self._namespaced_name(flag_name) - + value = None if check_before_waffle_callback: value = check_before_waffle_callback(namespaced_flag_name)