Exemplo n.º 1
0
 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))
Exemplo n.º 2
0
 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)
Exemplo n.º 3
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'])
Exemplo n.º 4
0
 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))
Exemplo n.º 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])
Exemplo n.º 6
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])
Exemplo n.º 7
0
 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))
Exemplo n.º 8
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, 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]
        )
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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'])
Exemplo n.º 12
0
    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'])
Exemplo n.º 13
0
    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']
Exemplo n.º 14
0
    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']
Exemplo n.º 15
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)
Exemplo n.º 16
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
                )