def post(self, request, username_or_email): """Allows support staff to alter a user's enrollment.""" try: user = User.objects.get( Q(username=username_or_email) | Q(email=username_or_email)) course_id = request.data['course_id'] course_key = CourseKey.from_string(course_id) old_mode = request.data['old_mode'] new_mode = request.data['new_mode'] reason = request.data['reason'] enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key) if enrollment.mode != old_mode: return HttpResponseBadRequest( 'User {username} is not enrolled with mode {old_mode}.'. format(username=user.username, old_mode=old_mode)) except KeyError as err: return HttpResponseBadRequest('The field {} is required.'.format( str(err))) except InvalidKeyError: return HttpResponseBadRequest('Could not parse course key.') except (CourseEnrollment.DoesNotExist, User.DoesNotExist): return HttpResponseBadRequest( 'Could not find enrollment for user {username} in course {course}.' .format(username=username_or_email, course=str(course_key))) try: # Wrapped in a transaction so that we can be sure the # ManualEnrollmentAudit record is always created correctly. with transaction.atomic(): update_enrollment(user.username, course_id, mode=new_mode, include_expired=True) manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit( request.user, enrollment.user.email, ENROLLED_TO_ENROLLED, reason=reason, enrollment=enrollment) if new_mode == CourseMode.CREDIT_MODE: provider_ids = get_credit_provider_attribute_values( course_key, 'id') credit_provider_attr = { 'namespace': 'credit', 'name': 'provider_id', 'value': provider_ids[0], } CourseEnrollmentAttribute.add_enrollment_attr( enrollment=enrollment, data_list=[credit_provider_attr]) entitlement = CourseEntitlement.get_fulfillable_entitlement_for_user_course_run( user=user, course_run_key=course_id) if entitlement is not None and entitlement.mode == new_mode: entitlement.set_enrollment( CourseEnrollment.get_enrollment(user, course_id)) return JsonResponse( ManualEnrollmentSerializer( instance=manual_enrollment).data) except CourseModeNotFoundError as err: return HttpResponseBadRequest(str(err))
def test_unenroll_not_enrolled_in_course(self): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['honor']) with pytest.raises(EnrollmentNotFoundError): api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='honor', is_active=False)
def test_update_enrollment(self): # Add fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEqual(result, get_result) result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='honor') self.assertEqual('honor', result['mode']) result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='verified') self.assertEqual('verified', result['mode'])
def post(self, request, username_or_email): """Allows support staff to alter a user's enrollment.""" try: user = User.objects.get( Q(username=username_or_email) | Q(email=username_or_email)) course_id = request.data['course_id'] course_key = CourseKey.from_string(course_id) old_mode = request.data['old_mode'] new_mode = request.data['new_mode'] reason = request.data['reason'] enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key) if enrollment.mode != old_mode: return HttpResponseBadRequest( u'User {username} is not enrolled with mode {old_mode}.'. format(username=user.username, old_mode=old_mode)) if new_mode == CourseMode.CREDIT_MODE: return HttpResponseBadRequest( u'Enrollment cannot be changed to credit mode.') except KeyError as err: return HttpResponseBadRequest(u'The field {} is required.'.format( text_type(err))) except InvalidKeyError: return HttpResponseBadRequest(u'Could not parse course key.') except (CourseEnrollment.DoesNotExist, User.DoesNotExist): return HttpResponseBadRequest( u'Could not find enrollment for user {username} in course {course}.' .format(username=username_or_email, course=six.text_type(course_key))) try: # Wrapped in a transaction so that we can be sure the # ManualEnrollmentAudit record is always created correctly. with transaction.atomic(): update_enrollment(user.username, course_id, mode=new_mode, include_expired=True) manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit( request.user, enrollment.user.email, ENROLLED_TO_ENROLLED, reason=reason, enrollment=enrollment) return JsonResponse( ManualEnrollmentSerializer( instance=manual_enrollment).data) except CourseModeNotFoundError as err: return HttpResponseBadRequest(text_type(err))
def test_update_enrollment_attributes(self): # Add fake course enrollment information to the fake data API fake_data_api.add_course( self.COURSE_ID, course_modes=['honor', 'verified', 'audit', 'credit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEqual(result, get_result) enrollment_attributes = [{ "namespace": "credit", "name": "provider_id", "value": "hogwarts", }] result = api.update_enrollment( self.USERNAME, self.COURSE_ID, mode='credit', enrollment_attributes=enrollment_attributes) self.assertEqual('credit', result['mode']) attributes = api.get_enrollment_attributes(self.USERNAME, self.COURSE_ID) self.assertEqual(enrollment_attributes[0], attributes[0])
def test_send_to_track(self): """ Make sure email sent to a registration track goes there. """ CourseMode.objects.create(mode_slug='test', course_id=self.course.id) for student in self.students: update_enrollment(student, str(self.course.id), 'test') test_email = { 'action': 'Send email', 'send_to': '["track:test"]', 'subject': 'test subject for test track', 'message': 'test message for test track', } response = self.client.post(self.send_mail_url, test_email) assert json.loads(response.content.decode('utf-8')) == self.success_content assert len([e.to[0] for e in mail.outbox]) == len([s.email for s in self.students])
def post(self, request, username_or_email): """Allows support staff to alter a user's enrollment.""" try: user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) course_id = request.data['course_id'] course_key = CourseKey.from_string(course_id) old_mode = request.data['old_mode'] new_mode = request.data['new_mode'] reason = request.data['reason'] enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key) if enrollment.mode != old_mode: return HttpResponseBadRequest(u'User {username} is not enrolled with mode {old_mode}.'.format( username=user.username, old_mode=old_mode )) if new_mode == CourseMode.CREDIT_MODE: return HttpResponseBadRequest(u'Enrollment cannot be changed to credit mode.') except KeyError as err: return HttpResponseBadRequest(u'The field {} is required.'.format(text_type(err))) except InvalidKeyError: return HttpResponseBadRequest(u'Could not parse course key.') except (CourseEnrollment.DoesNotExist, User.DoesNotExist): return HttpResponseBadRequest( u'Could not find enrollment for user {username} in course {course}.'.format( username=username_or_email, course=six.text_type(course_key) ) ) try: # Wrapped in a transaction so that we can be sure the # ManualEnrollmentAudit record is always created correctly. with transaction.atomic(): update_enrollment(user.username, course_id, mode=new_mode, include_expired=True) manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit( request.user, enrollment.user.email, ENROLLED_TO_ENROLLED, reason=reason, enrollment=enrollment ) return JsonResponse(ManualEnrollmentSerializer(instance=manual_enrollment).data) except CourseModeNotFoundError as err: return HttpResponseBadRequest(text_type(err))
def test_send_to_track(self): """ Make sure email sent to a registration track goes there. """ CourseMode.objects.create(mode_slug='test', course_id=self.course.id) for student in self.students: update_enrollment(student, six.text_type(self.course.id), 'test') test_email = { 'action': 'Send email', 'send_to': '["track:test"]', 'subject': 'test subject for test track', 'message': 'test message for test track', } response = self.client.post(self.send_mail_url, test_email) self.assertEquals(json.loads(response.content), self.success_content) self.assertItemsEqual( [e.to[0] for e in mail.outbox], [s.email for s in self.students] )
def test_send_to_track_other_enrollments(self): """ Failing test for EDUCATOR-217: verifies that emails are only sent to users in a specific track if they're in that track in the course the email is being sent from. """ # Create a mode and designate an enrolled user to be placed in that mode CourseMode.objects.create(mode_slug='test_mode', course_id=self.course.id) test_mode_student = self.students[0] update_enrollment(test_mode_student, six.text_type(self.course.id), 'test_mode') # Take another user already enrolled in the course, then enroll them in # another course but in that same test mode test_mode_student_other_course = self.students[1] other_course = CourseFactory.create() CourseMode.objects.create(mode_slug='test_mode', course_id=other_course.id) CourseEnrollmentFactory.create(user=test_mode_student_other_course, course_id=other_course.id) update_enrollment(test_mode_student_other_course, six.text_type(other_course.id), 'test_mode') # Send the emails... test_email = { 'action': 'Send email', 'send_to': '["track:test_mode"]', 'subject': 'test subject for test_mode track', 'message': 'test message for test_mode track', } response = self.client.post(self.send_mail_url, test_email) assert json.loads( response.content.decode('utf-8')) == self.success_content # Only the the student in the test mode in the course the email was # sent from should receive an email assert len(mail.outbox) == 1 assert mail.outbox[0].to[0] == test_mode_student.email
def test_send_to_track_other_enrollments(self): """ Failing test for EDUCATOR-217: verifies that emails are only sent to users in a specific track if they're in that track in the course the email is being sent from. """ # Create a mode and designate an enrolled user to be placed in that mode CourseMode.objects.create(mode_slug='test_mode', course_id=self.course.id) test_mode_student = self.students[0] update_enrollment(test_mode_student, six.text_type(self.course.id), 'test_mode') # Take another user already enrolled in the course, then enroll them in # another course but in that same test mode test_mode_student_other_course = self.students[1] other_course = CourseFactory.create() CourseMode.objects.create(mode_slug='test_mode', course_id=other_course.id) CourseEnrollmentFactory.create( user=test_mode_student_other_course, course_id=other_course.id ) update_enrollment(test_mode_student_other_course, six.text_type(other_course.id), 'test_mode') # Send the emails... test_email = { 'action': 'Send email', 'send_to': '["track:test_mode"]', 'subject': 'test subject for test_mode track', 'message': 'test message for test_mode track', } response = self.client.post(self.send_mail_url, test_email) self.assertEquals(json.loads(response.content), self.success_content) # Only the the student in the test mode in the course the email was # sent from should receive an email self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to[0], test_mode_student.email)
def assert_update_enrollment(self, mode, is_active=True, include_expired=False): """ Dry method for updating enrollment.""" result = api.update_enrollment( self.USERNAME, self.COURSE_ID, mode=mode, is_active=is_active, include_expired=include_expired ) self.assertEqual(mode, result['mode']) self.assertIsNotNone(result) self.assertEqual(result['student'], self.USERNAME) self.assertEqual(result['course']['course_id'], self.COURSE_ID) self.assertEqual(result['mode'], mode) if is_active: self.assertTrue(result['is_active']) else: self.assertFalse(result['is_active'])
def test_unenroll(self, course_modes, mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=mode) self.assertIsNotNone(result) self.assertEqual(result['student'], self.USERNAME) self.assertEqual(result['course']['course_id'], self.COURSE_ID) self.assertEqual(result['mode'], mode) self.assertTrue(result['is_active']) result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode=mode, is_active=False) self.assertIsNotNone(result) self.assertEqual(result['student'], self.USERNAME) self.assertEqual(result['course']['course_id'], self.COURSE_ID) self.assertEqual(result['mode'], mode) self.assertFalse(result['is_active'])
def test_unenroll(self, course_modes, mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=mode) assert result is not None assert result['student'] == self.USERNAME assert result['course']['course_id'] == self.COURSE_ID assert result['mode'] == mode assert result['is_active'] result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode=mode, is_active=False) assert result is not None assert result['student'] == self.USERNAME assert result['course']['course_id'] == self.COURSE_ID assert result['mode'] == mode assert not result['is_active']
def assert_update_enrollment(self, mode, is_active=True, include_expired=False): """ Dry method for updating enrollment.""" result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode=mode, is_active=is_active, include_expired=include_expired) assert mode == result['mode'] assert result is not None assert result['student'] == self.USERNAME assert result['course']['course_id'] == self.COURSE_ID assert result['mode'] == mode if is_active: assert result['is_active'] else: assert not result['is_active']
def post(self, request): # pylint: disable=too-many-statements """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}]." .format(mode=mode) }) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) }) embargo_response = embargo_api.get_embargo_response( request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status." ).format(value=is_active) }) explicit_linked_enterprise = request.data.get( 'linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled( ): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment( username, text_type(course_id), None) except EnterpriseApiException as error: log.exception( u"An unexpected error occurred while creating the new EnterpriseCourseEnrollment " u"for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': text_type(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, text_type(course_id)) mode_changed = enrollment and mode is not None and enrollment[ 'mode'] != mode active_changed = enrollment and is_active is not None and enrollment[ 'is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get( mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if missing_attrs: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode)) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) try: add_user_to_cohort(cohort, user) except ValueError: # user already in cohort, probably because they were un-enrolled and re-enrolled log.exception('Cohort re-addition') email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info( u'The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentExistsError as error: log.warning( u'An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception( u"An error occurred while creating the new course enrollment for user " u"[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'").format( username=username, course_id=course_id) }) except CourseUserGroup.DoesNotExist: log.exception(u'Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment( username, text_type(course_id)) audit_log('enrollment_change_requested', course_id=text_type(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id)
def post(self, request): # pylint: disable=too-many-statements """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) explicit_linked_enterprise = request.data.get('linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment(username, text_type(course_id), None) except EnterpriseApiException as error: log.exception(u"An unexpected error occurred while creating the new EnterpriseCourseEnrollment " u"for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': text_type(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, text_type(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if missing_attrs: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions ) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes ) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) try: add_user_to_cohort(cohort, user) except ValueError: # user already in cohort, probably because they were un-enrolled and re-enrolled log.exception('Cohort re-addition') email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info(u'The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: log.warning(u'An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception(u"An error occurred while creating the new course enrollment for user " u"[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } ) except CourseUserGroup.DoesNotExist: log.exception(u'Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, text_type(course_id)) audit_log( 'enrollment_change_requested', course_id=text_type(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id )