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: