Beispiel #1
0
 def test_get_all_enrollments(self, enrollments):
     for enrollment in enrollments:
         fake_data_api.add_course(enrollment['course_id'], course_modes=enrollment['course_modes'])
         api.add_enrollment(self.USERNAME, enrollment['course_id'], enrollment['mode'])
     result = api.get_enrollments(self.USERNAME)
     self.assertEqual(len(enrollments), len(result))
     for result_enrollment in result:
         self.assertIn(
             result_enrollment['course']['course_id'],
             [enrollment['course_id'] for enrollment in enrollments]
         )
Beispiel #2
0
 def test_get_all_enrollments(self, enrollments):
     for enrollment in enrollments:
         fake_data_api.add_course(enrollment['course_id'],
                                  course_modes=enrollment['course_modes'])
         api.add_enrollment(self.USERNAME, enrollment['course_id'],
                            enrollment['mode'])
     result = api.get_enrollments(self.USERNAME)
     assert len(enrollments) == len(result)
     for result_enrollment in result:
         assert result_enrollment['course']['course_id'] in [
             enrollment['course_id'] for enrollment in enrollments
         ]
Beispiel #3
0
    def handle(self, *args, **options):
        """
        Get and enroll a user in the given course. Mode is optional and defers to the enrollment API for defaults.
        """
        email = options['email'][0]
        course = options['course'][0]
        mode = options['mode']

        user = User.objects.get(email=email)
        try:
            add_enrollment(user.username, course, mode=mode)
        except CourseEnrollmentExistsError:
            # If the user is already enrolled in the course, do nothing.
            pass
    def handle(self, *args, **options):
        """
        Get and enroll a user in the given course. Mode is optional and defers to the enrollment API for defaults.
        """
        email = options['email'][0]
        course = options['course'][0]
        mode = options['mode']

        user = User.objects.get(email=email)
        try:
            add_enrollment(user.username, course, mode=mode)
        except CourseEnrollmentExistsError:
            # If the user is already enrolled in the course, do nothing.
            pass
Beispiel #5
0
    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])
Beispiel #6
0
 def assert_add_modes_with_enrollment(self, enrollment_mode):
     """ Dry method for adding fake course enrollment information to fake
     data API and enroll the student in the course. """
     fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit'])
     result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=enrollment_mode)
     get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
     self.assertEqual(result, get_result)
     # set the course verify mode as expire.
     fake_data_api.set_expired_mode(self.COURSE_ID)
Beispiel #7
0
    def test_enroll(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)

        get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
        self.assertEqual(result, get_result)
Beispiel #8
0
 def test_enroll_no_mode_success(self, course_modes, expected_mode):
     # Add a fake course enrollment information to the fake data API
     fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes)
     with patch('openedx.core.djangoapps.enrollments.api.CourseMode.modes_for_course') as mock_modes_for_course:
         mock_course_modes = [Mock(slug=mode) for mode in course_modes]
         mock_modes_for_course.return_value = mock_course_modes
         # Enroll in the course and verify the URL we get sent to
         result = api.add_enrollment(self.USERNAME, self.COURSE_ID)
         self.assertIsNotNone(result)
         self.assertEqual(result['student'], self.USERNAME)
         self.assertEqual(result['course']['course_id'], self.COURSE_ID)
         self.assertEqual(result['mode'], expected_mode)
Beispiel #9
0
    def test_enroll(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

        get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
        assert result == get_result
Beispiel #10
0
    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'])
Beispiel #11
0
    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
                )
Beispiel #12
0
 def _enroll(self, course_key, user, mode=CourseMode.DEFAULT_MODE_SLUG):
     """ Enroll the user in the course. """
     add_enrollment(user.username, six.text_type(course_key), mode)
Beispiel #13
0
 def test_enroll_no_mode_error(self, course_modes):
     # 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 that we raise CourseModeNotFoundError
     with pytest.raises(CourseModeNotFoundError):
         api.add_enrollment(self.USERNAME, self.COURSE_ID)
Beispiel #14
0
 def test_data_api_config_error(self):
     # Enroll in the course and verify the URL we get sent to
     with pytest.raises(EnrollmentApiLoadError):
         api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit')
Beispiel #15
0
 def _enroll(self, course_key, user, mode=CourseMode.DEFAULT_MODE_SLUG):
     """ Enroll the user in the course. """
     add_enrollment(user.username, unicode(course_key), mode)
Beispiel #16
0
 def test_prof_ed_enroll(self):
     # Add a fake course enrollment information to the fake data API
     fake_data_api.add_course(self.COURSE_ID, course_modes=['professional'])
     # Enroll in the course and verify the URL we get sent to
     with pytest.raises(CourseModeNotFoundError):
         api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='verified')
Beispiel #17
0
    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)
Beispiel #18
0
def lms_enroll_user_in_course(
    username,
    course_id,
    mode,
    enterprise_uuid,
    is_active=True,
):
    """
    Enrollment function meant to be called by edx-enterprise to replace the
    current uses of the EnrollmentApiClient
    The REST enrollment endpoint may also eventually also want to reuse this function
    since it's a subset of what the endpoint handles

    Unlike the REST endpoint, this function does not check for enterprise enabled, or user api key
    permissions etc. Those concerns are still going to be used by REST endpoint but this function
    is meant for use from within edx-enterprise hence already presume such privileges.

    Arguments:
     - username (str): User name
     - course_id (obj) : Course key obtained using CourseKey.from_string(course_id_input)
     - mode (CourseMode): course mode
     - enterprise_uuid (str): id to identify the enterprise to enroll under
     - is_active (bool): Optional. A Boolean value that indicates whether the
        enrollment is to be set to inactive (if False). Usually we want a True if enrolling anew.

    Returns: A serializable dictionary of the new course enrollment. If it hits
     `CourseEnrollmentExistsError` then it logs the error and returns None.
    """
    user = _validate_enrollment_inputs(username, course_id)

    with transaction.atomic():
        try:
            response = enrollment_api.add_enrollment(
                username,
                str(course_id),
                mode=mode,
                is_active=is_active,
                enrollment_attributes=None,
                enterprise_uuid=enterprise_uuid,
            )
            log.info('The user [%s] has been enrolled in course run [%s].',
                     username, course_id)
            return response
        except CourseEnrollmentExistsError as error:  # pylint: disable=unused-variable
            log.warning(
                'An enrollment already exists for user [%s] in course run [%s].',
                username, course_id)
            return None
        except CourseEnrollmentError as error:
            log.exception(
                "An error occurred while creating the new course enrollment for user "
                "[%s] in course run [%s]", username, course_id)
            raise error
        finally:
            # Assumes that the ecommerce service uses an API key to authenticate.
            current_enrollment = enrollment_api.get_enrollment(
                username, str(course_id))
            audit_log('enrollment_change_requested',
                      course_id=str(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)