From ab52966fdb7f94905997e43f1e35b07768d30967 Mon Sep 17 00:00:00 2001 From: Clinton Blackburn Date: Thu, 1 Jun 2017 15:19:27 -0400 Subject: [PATCH] Updated Enrollment API to always store enrollment attributes Enrollment attributes are now always stored, regardless of whether an enrollment is being created or updated. This will ensure that learners who purchase paid modes, without first enrolling in a non-paid mode, can request refunds if they choose to un-enroll from a course run within the refund window. LEARNER-1282 --- common/djangoapps/enrollment/api.py | 12 +++-- .../djangoapps/enrollment/tests/test_views.py | 53 ++++++++++++++++++- common/djangoapps/enrollment/views.py | 8 ++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index 2833bcc3b0..b8c4a535ca 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -142,7 +142,7 @@ def get_enrollment(user_id, course_id): return _data_api().get_course_enrollment(user_id, course_id) -def add_enrollment(user_id, course_id, mode=None, is_active=True): +def add_enrollment(user_id, course_id, mode=None, is_active=True, enrollment_attributes=None): """Enrolls a user in a course. Enrolls a user in a course. If the mode is not specified, this will default to `CourseMode.DEFAULT_MODE_SLUG`. @@ -150,12 +150,11 @@ def add_enrollment(user_id, course_id, mode=None, is_active=True): Arguments: user_id (str): The user to enroll. course_id (str): The course to enroll the user in. - - Keyword Arguments: mode (str): Optional argument for the type of enrollment to create. Ex. 'audit', 'honor', 'verified', 'professional'. If not specified, this defaults to the default course mode. is_active (boolean): Optional argument for making the new enrollment inactive. If not specified, is_active defaults to True. + enrollment_attributes (list): Attributes to be set the enrollment. Returns: A serializable dictionary of the new course enrollment. @@ -194,7 +193,12 @@ def add_enrollment(user_id, course_id, mode=None, is_active=True): if mode is None: mode = _default_course_mode(course_id) validate_course_mode(course_id, mode, is_active=is_active) - return _data_api().create_course_enrollment(user_id, course_id, mode, is_active) + enrollment = _data_api().create_course_enrollment(user_id, course_id, mode, is_active) + + if enrollment_attributes is not None: + set_enrollment_attributes(user_id, course_id, enrollment_attributes) + + return enrollment def update_enrollment(user_id, course_id, mode=None, is_active=None, enrollment_attributes=None, include_expired=False): diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 90817c36e8..a0f302e542 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -19,6 +19,8 @@ from mock import patch from nose.plugins.attrib import attr from rest_framework import status from rest_framework.test import APITestCase +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls_range from course_modes.models import CourseMode from enrollment import api @@ -35,8 +37,6 @@ from student.roles import CourseStaffRole from student.tests.factories import AdminFactory, CourseModeFactory, UserFactory from util.models import RateLimitConfiguration from util.testing import UrlResetMixin -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls_range class EnrollmentTestMixin(object): @@ -997,6 +997,55 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente 'No request was made to the mocked enterprise-course-enrollment API' ) + def test_enrollment_attributes_always_written(self): + """ Enrollment attributes should always be written, regardless of whether + the enrollment is being created or updated. + """ + course_key = self.course.id + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]: + CourseModeFactory.create( + course_id=course_key, + mode_slug=mode, + mode_display_name=mode, + ) + + # Creating a new enrollment should write attributes + order_number = 'EDX-1000' + enrollment_attributes = [{ + 'namespace': 'order', + 'name': 'order_number', + 'value': order_number, + }] + mode = CourseMode.VERIFIED + self.assert_enrollment_status( + as_server=True, + is_active=True, + mode=mode, + enrollment_attributes=enrollment_attributes + ) + enrollment = CourseEnrollment.objects.get(user=self.user, course_id=course_key) + self.assertTrue(enrollment.is_active) + self.assertEqual(enrollment.mode, CourseMode.VERIFIED) + self.assertEqual(enrollment.attributes.get(namespace='order', name='order_number').value, order_number) + + # Updating an enrollment should update attributes + order_number = 'EDX-2000' + enrollment_attributes = [{ + 'namespace': 'order', + 'name': 'order_number', + 'value': order_number, + }] + mode = CourseMode.DEFAULT_MODE_SLUG + self.assert_enrollment_status( + as_server=True, + mode=mode, + enrollment_attributes=enrollment_attributes + ) + enrollment.refresh_from_db() + self.assertTrue(enrollment.is_active) + self.assertEqual(enrollment.mode, mode) + self.assertEqual(enrollment.attributes.get(namespace='order', name='order_number').value, order_number) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class EnrollmentEmbargoTest(EnrollmentTestMixin, UrlResetMixin, ModuleStoreTestCase): diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index 65cdf8e8ad..88c3ed5d24 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -641,7 +641,13 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ) else: # Will reactivate inactive enrollments. - response = api.add_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) + response = api.add_enrollment( + username, + unicode(course_id), + mode=mode, + is_active=is_active, + enrollment_attributes=enrollment_attributes + ) email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: