Exemplo n.º 1
0
def _unenroll_entitlement(course_entitlement, course_run_key):
    """
    Internal method to handle the details of Unenrolling a User in a Course Run.
    """
    CourseEnrollment.unenroll(course_entitlement.user,
                              course_run_key,
                              skip_refund=True)
 def test_not_enrolled_public_course(self):
     """
     Verify behaviour when accessing course blocks for a public course as a user not enrolled in course.
     """
     self.query_params['username'] = ''
     CourseEnrollment.unenroll(self.user, self.course_key)
     self.verify_response(cacheable=True)
Exemplo n.º 3
0
    def test_unenroll_entitlement_with_audit_course_enrollment(
            self, mock_refund, mock_get_course_uuid):
        """
        Test that entitlement is not refunded if un-enroll is called on audit course un-enroll.
        """
        self.enrollment.mode = CourseMode.AUDIT
        self.enrollment.user = self.user
        self.enrollment.save()
        entitlement = CourseEntitlementFactory.create(user=self.user)
        mock_get_course_uuid.return_value = entitlement.course_uuid
        CourseEnrollment.unenroll(self.user, self.course.id)

        assert not mock_refund.called
        entitlement.refresh_from_db()
        assert entitlement.expired_at is None

        self.enrollment.mode = CourseMode.VERIFIED
        self.enrollment.is_active = True
        self.enrollment.save()
        entitlement.enrollment_course_run = self.enrollment
        entitlement.save()
        CourseEnrollment.unenroll(self.user, self.course.id)

        assert mock_refund.called
        entitlement.refresh_from_db()
        assert entitlement.expired_at < now()
Exemplo n.º 4
0
    def test_choose_mode_audit_enroll_on_post(self):
        audit_mode = 'audit'
        # Create the course modes
        for mode in (audit_mode, 'verified'):
            min_price = 0 if mode in [audit_mode] else 1
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=min_price)

        # Assert learner is not enrolled in Audit track pre-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        assert mode is None
        assert is_active is None

        # Choose the audit mode (POST request)
        choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)])
        self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[audit_mode])

        # Assert learner is enrolled in Audit track post-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        assert mode == audit_mode
        assert is_active

        # Unenroll learner from Audit track and confirm the enrollment record is now 'inactive'
        CourseEnrollment.unenroll(self.user, self.course.id)
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        assert mode == audit_mode
        assert not is_active

        # Choose the audit mode again
        self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[audit_mode])

        # Assert learner is again enrolled in Audit track post-POST-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        assert mode == audit_mode
        assert is_active
 def test_get_authenticated_user_not_enrolled(self, has_previously_enrolled):
     if has_previously_enrolled:
         # Create an enrollment, then unenroll to set is_active to False
         CourseEnrollment.enroll(self.user, self.course.id)
         CourseEnrollment.unenroll(self.user, self.course.id)
     response = self.client.get(self.url)
     assert response.status_code == 401
Exemplo n.º 6
0
    def delete(self, request, course_key_string):
        course_key = CourseKey.from_string(course_key_string)

        email = request.data.get("email", None)
        validate_param_exist(email, "email")

        try:
            user = User.objects.get(email=email)
        except Exception:  # pylint: disable=broad-except
            msg = {
                "error":
                "Could not find user by email address '{email}'".format(
                    email=email)
            }
            return Response(msg, 404)

        auth.get_user_permissions(request.user, course_key)

        auth.remove_users(request.user, CourseStaffRole(course_key), user)
        auth.remove_users(request.user, CourseInstructorRole(course_key), user)

        CourseEnrollment.unenroll(user, course_key)

        msg = "'{email}''s permissions are revoked from '{course_key}'".format(
            email=email, course_key=course_key)
        log.info(msg)

        return Response(
            {'message': "User is removed from {}.".format(course_key)})
Exemplo n.º 7
0
    def test_unenrolled_from_some_courses(self):
        # Enroll in several courses in the org
        self._create_courses_and_enrollments(
            (self.TEST_ORG, True),
            (self.TEST_ORG, True),
            (self.TEST_ORG, True),
            ("org_alias", True)
        )

        # Set a preference for the aliased course
        self._set_opt_in_pref(self.user, "org_alias", False)

        # Unenroll from the aliased course
        CourseEnrollment.unenroll(self.user, self.courses[3].id, skip_refund=True)

        # Expect that the preference still applies,
        # and all the enrollments should appear in the list
        output = self._run_command(self.TEST_ORG, other_names=["org_alias"])
        self._assert_output(
            output,
            (self.user, self.courses[0].id, False),
            (self.user, self.courses[1].id, False),
            (self.user, self.courses[2].id, False),
            (self.user, self.courses[3].id, False)
        )
Exemplo n.º 8
0
    def test_generate_user_is_not_enrolled(self):
        # Unenroll the user
        CourseEnrollment.unenroll(self.student, self.EXISTED_COURSE_KEY_2)

        # Can no longer regenerate certificates for the user
        response = self._generate(course_key=self.EXISTED_COURSE_KEY_2,
                                  username=self.STUDENT_USERNAME)
        assert response.status_code == 400
Exemplo n.º 9
0
    def test_regenerate_user_is_not_enrolled(self):
        # Unenroll the user
        CourseEnrollment.unenroll(self.student, self.CERT_COURSE_KEY)

        # Can no longer regenerate certificates for the user
        response = self._regenerate(course_key=self.CERT_COURSE_KEY,
                                    username=self.STUDENT_USERNAME)
        self.assertEqual(response.status_code, 400)
Exemplo n.º 10
0
    def setUp(self):
        super(PermissionTests, self).setUp()
        self.user = UserFactory()

        self.course_id = CourseLocator('MITx', '000', 'Perm_course')
        CourseModeFactory(mode_slug='verified', course_id=self.course_id)
        CourseModeFactory(mode_slug='masters', course_id=self.course_id)
        CourseModeFactory(mode_slug='professional', course_id=self.course_id)
        CourseEnrollment.unenroll(self.user, self.course_id)
Exemplo n.º 11
0
    def setUp(self):
        super().setUp()
        self.user = UserFactory()
        self.course = CourseFactory(enable_proctored_exams=True)

        self.course_id = self.course.id  # pylint: disable=no-member
        CourseModeFactory(mode_slug='verified', course_id=self.course_id)
        CourseModeFactory(mode_slug='masters', course_id=self.course_id)
        CourseModeFactory(mode_slug='professional', course_id=self.course_id)
        CourseEnrollment.unenroll(self.user, self.course_id)
Exemplo n.º 12
0
 def test_existing_inactive_enrollment(self):
     """
     If the user has an inactive enrollment for the course, the view should behave as if the
     user has no enrollment.
     """
     # Create an inactive enrollment
     CourseEnrollment.enroll(self.user, self.course.id)
     CourseEnrollment.unenroll(self.user, self.course.id, True)
     assert not CourseEnrollment.is_enrolled(self.user, self.course.id)
     assert get_enrollment(self.user.username, str(self.course.id)) is not None
Exemplo n.º 13
0
 def test_existing_inactive_enrollment(self):
     """
     If the user has an inactive enrollment for the course, the view should behave as if the
     user has no enrollment.
     """
     # Create an inactive enrollment
     CourseEnrollment.enroll(self.user, self.course.id)
     CourseEnrollment.unenroll(self.user, self.course.id, True)
     self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
     self.assertIsNotNone(get_enrollment(self.user.username, six.text_type(self.course.id)))
Exemplo n.º 14
0
    def test_cea_enrolls_only_one_user(self):
        """
        Tests that a CourseEnrollmentAllowed can be used by just one user.
        If the user changes e-mail and then a second user tries to enroll with the same accepted e-mail,
        the second enrollment should fail.
        However, the original user can reuse the CEA many times.
        """

        cea = CourseEnrollmentAllowedFactory(
            email='*****@*****.**',
            course_id=self.course.id,
            auto_enroll=False,
        )
        # Still unlinked
        assert cea.user is None

        user1 = UserFactory.create(username="******", email="*****@*****.**", password="******")
        user2 = UserFactory.create(username="******", email="*****@*****.**", password="******")

        assert not CourseEnrollment.objects.filter(course_id=self.course.id, user=user1).exists()

        user1.email = '*****@*****.**'
        user1.save()

        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        assert CourseEnrollment.objects.filter(course_id=self.course.id, user=user1).exists()

        # The CEA is now linked
        cea.refresh_from_db()
        assert cea.user == user1

        # user2 wants to enroll too, (ab)using the same allowed e-mail, but cannot
        user1.email = '*****@*****.**'
        user1.save()
        user2.email = '*****@*****.**'
        user2.save()
        with pytest.raises(EnrollmentClosedError):
            CourseEnrollment.enroll(user2, self.course.id, check_access=True)

        # CEA still linked to user1. Also after unenrolling
        cea.refresh_from_db()
        assert cea.user == user1

        CourseEnrollment.unenroll(user1, self.course.id)

        cea.refresh_from_db()
        assert cea.user == user1

        # Enroll user1 again. Because it's the original owner of the CEA, the enrollment is allowed
        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        # Still same
        cea.refresh_from_db()
        assert cea.user == user1
Exemplo n.º 15
0
    def test_unenrolled_from_all_courses(self, opt_in_pref):
        # Enroll in the course and set a preference
        self._create_courses_and_enrollments((self.TEST_ORG, True))
        self._set_opt_in_pref(self.user, self.TEST_ORG, opt_in_pref)

        # Unenroll from the course
        CourseEnrollment.unenroll(self.user, self.courses[0].id, skip_refund=True)

        # Enrollments should still appear in the outpu
        output = self._run_command(self.TEST_ORG)
        self._assert_output(output, (self.user, self.courses[0].id, opt_in_pref))
Exemplo n.º 16
0
    def test_unenrollment_filter_prevent_unenroll(self):
        """
        Test prevent the user's unenrollment through a pipeline step.

        Expected result:
            - CourseUnenrollmentStarted is triggered and executes TestUnenrollmentPipelineStep.
            - The user can't unenroll.
        """
        CourseEnrollment.enroll(self.user,
                                self.course.id,
                                mode="no-id-professional")

        with self.assertRaises(UnenrollmentNotAllowed):
            CourseEnrollment.unenroll(self.user, self.course.id)
Exemplo n.º 17
0
    def test_unenrollment_without_filter_configuration(self):
        """
        Test usual unenrollment process without filter's intervention.

        Expected result:
            - CourseUnenrollmentStarted does not have any effect on the unenrollment process.
            - The unenrollment process ends successfully.
        """
        CourseEnrollment.enroll(self.user, self.course.id, mode="audit")

        CourseEnrollment.unenroll(self.user, self.course.id)

        self.assertFalse(
            CourseEnrollment.is_enrolled(self.user, self.course.id))
Exemplo n.º 18
0
    def test_schedule_is_reset_to_availability_date(self):
        """ Test that a switch to audit enrollment resets to the availability date, not current time. """
        original_start = self.schedule.start_date

        # Switch to verified, confirm we change start date
        CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.VERIFIED)
        self.schedule.refresh_from_db()
        assert self.schedule.start_date != original_start

        CourseEnrollment.unenroll(self.user, self.course.id)

        # Switch back to audit, confirm we change back to original availability date
        CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT)
        self.schedule.refresh_from_db()
        assert self.schedule.start_date == original_start
Exemplo n.º 19
0
    def test_unenrollment_filter_executed(self):
        """
        Test whether the student unenrollment filter is triggered before the user's
        unenrollment process.

        Expected result:
            - CourseUnenrollmentStarted is triggered and executes TestUnenrollmentPipelineStep.
            - The user's profile has unenrolled_from in its meta field.
        """
        CourseEnrollment.enroll(self.user, self.course.id, mode="audit")

        CourseEnrollment.unenroll(self.user, self.course.id)

        self.assertFalse(
            CourseEnrollment.is_enrolled(self.user, self.course.id))
Exemplo n.º 20
0
    def test_get_authenticated_user_not_enrolled(self, has_previously_enrolled):
        if has_previously_enrolled:
            # Create an enrollment, then unenroll to set is_active to False
            CourseEnrollment.enroll(self.user, self.course.id)
            CourseEnrollment.unenroll(self.user, self.course.id)
        response = self.client.get(self.url)
        assert response.status_code == 200

        course_tools = response.data.get('course_tools')
        assert len(course_tools) == 0

        dates_widget = response.data.get('dates_widget')
        assert dates_widget
        date_blocks = dates_widget.get('course_date_blocks')
        assert all((block.get('title') != '') for block in date_blocks)
        assert all(block.get('date') for block in date_blocks)
    def test_get_course_list(self):
        """
        Test getting courses
        """
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location)

        # get dashboard
        courses_list = list(get_course_enrollments(self.student, None, []))
        assert len(courses_list) == 1
        assert courses_list[0].course_id == course_location

        CourseEnrollment.unenroll(self.student, course_location)
        # get dashboard
        courses_list = list(get_course_enrollments(self.student, None, []))
        assert len(courses_list) == 0
Exemplo n.º 22
0
    def test_enrollment_non_existent_user(self):
        # Testing enrollment of newly unsaved user (i.e. no database entry)
        user = User(username="******", email="*****@*****.**")
        course_id = CourseLocator("edX", "Test101", "2013")

        assert not CourseEnrollment.is_enrolled(user, course_id)

        # Unenroll does nothing
        CourseEnrollment.unenroll(user, course_id)
        self.assert_no_events_were_emitted()

        # Implicit save() happens on new User object when enrolling, so this
        # should still work
        CourseEnrollment.enroll(user, course_id)
        assert CourseEnrollment.is_enrolled(user, course_id)
        self.assert_enrollment_event_was_emitted(user, course_id)
 def test_public_course_all_blocks_and_empty_username(self):
     """
     Verify behaviour when specifying both all_blocks and username='', and ensure the response is not cached.
     """
     self.query_params['username'] = ''
     self.query_params['all_blocks'] = True
     # Verify response for a regular user.
     self.verify_response(403, cacheable=False)
     # Verify response for an unenrolled user.
     CourseEnrollment.unenroll(self.user, self.course_key)
     self.verify_response(403, cacheable=False)
     # Verify response for an anonymous user.
     self.client.logout()
     self.verify_response(403, cacheable=False)
     # Verify response for a staff user.
     self.client.login(username=self.admin_user.username, password='******')
     self.verify_response(cacheable=False)
Exemplo n.º 24
0
    def test_unenrollment_completed_event_emitted(self):
        """
        Test whether the student un-enrollment completed event is sent after the
        user's unenrollment process.

        Expected result:
            - COURSE_UNENROLLMENT_COMPLETED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        event_receiver = mock.Mock(
            side_effect=self._event_receiver_side_effect)
        COURSE_UNENROLLMENT_COMPLETED.connect(event_receiver)

        CourseEnrollment.unenroll(self.user, self.course.id)

        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal":
                COURSE_UNENROLLMENT_COMPLETED,
                "sender":
                None,
                "enrollment":
                CourseEnrollmentData(
                    user=UserData(
                        pii=UserPersonalData(
                            username=self.user.username,
                            email=self.user.email,
                            name=self.user.profile.name,
                        ),
                        id=self.user.id,
                        is_active=self.user.is_active,
                    ),
                    course=CourseData(
                        course_key=self.course.id,
                        display_name=self.course.display_name,
                    ),
                    mode=enrollment.mode,
                    is_active=False,
                    creation_date=enrollment.created,
                ),
            }, event_receiver.call_args.kwargs)
Exemplo n.º 25
0
 def test_change_to_default_if_verified_not_active(self):
     """
     Tests that one can renroll for a course if one has already unenrolled
     """
     # enroll student
     CourseEnrollment.enroll(self.user, self.course.id, mode='verified')
     # now unenroll student:
     CourseEnrollment.unenroll(self.user, self.course.id)
     # check that they are verified but inactive
     enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(
         self.user, self.course.id)
     assert not is_active
     assert enrollment_mode == 'verified'
     # now enroll them through the view:
     response = self._enroll_through_view(self.course)
     assert response.status_code == 200
     enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(
         self.user, self.course.id)
     assert is_active
     assert enrollment_mode == CourseMode.DEFAULT_MODE_SLUG
Exemplo n.º 26
0
    def unenroll(self, user):
        """
        Un-enroll user from the related course.

        Args:
            user (User): User object
        """
        enrollment = CourseEnrollment.objects.filter(
            user=user, course=self.course).first()
        if not enrollment:
            return

        certificate_info = cert_info(user, enrollment.course_overview)
        if certificate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
            exception_msg = (
                f'Cannot un-enroll user: {user} from course: {enrollment.course_overview.display_name_with_default}'
            )
            logger.exception(exception_msg)
            raise Exception(exception_msg)

        CourseEnrollment.unenroll(user, self.course)
Exemplo n.º 27
0
    def test_enrollment_multiple_classes(self):
        user = User(username="******", email="*****@*****.**")
        course_id1 = CourseLocator("edX", "Test101", "2013")
        course_id2 = CourseLocator("MITx", "6.003z", "2012")

        CourseEnrollment.enroll(user, course_id1)
        self.assert_enrollment_event_was_emitted(user, course_id1)
        CourseEnrollment.enroll(user, course_id2)
        self.assert_enrollment_event_was_emitted(user, course_id2)
        assert CourseEnrollment.is_enrolled(user, course_id1)
        assert CourseEnrollment.is_enrolled(user, course_id2)

        CourseEnrollment.unenroll(user, course_id1)
        self.assert_unenrollment_event_was_emitted(user, course_id1)
        assert not CourseEnrollment.is_enrolled(user, course_id1)
        assert CourseEnrollment.is_enrolled(user, course_id2)

        CourseEnrollment.unenroll(user, course_id2)
        self.assert_unenrollment_event_was_emitted(user, course_id2)
        assert not CourseEnrollment.is_enrolled(user, course_id1)
        assert not CourseEnrollment.is_enrolled(user, course_id2)
Exemplo n.º 28
0
    def handle(self, *args, **options):
        source_key = CourseKey.from_string(options['source_course'])
        dest_keys = []
        for course_key in options['dest_course_list']:
            dest_keys.append(CourseKey.from_string(course_key))

        source_students = User.objects.filter(
            courseenrollment__course_id=source_key)

        for user in source_students:
            with transaction.atomic():
                print(f'Moving {user.username}.')
                # Find the old enrollment.
                enrollment = CourseEnrollment.objects.get(user=user,
                                                          course_id=source_key)

                # Move the Student between the classes.
                mode = enrollment.mode
                old_is_active = enrollment.is_active
                CourseEnrollment.unenroll(user, source_key, skip_refund=True)
                print('Unenrolled {} from {}'.format(user.username,
                                                     str(source_key)))

                for dest_key in dest_keys:
                    if CourseEnrollment.is_enrolled(user, dest_key):
                        # Un Enroll from source course but don't mess
                        # with the enrollment in the destination course.
                        msg = 'Skipping {}, already enrolled in destination course {}'
                        print(msg.format(user.username, str(dest_key)))
                    else:
                        new_enrollment = CourseEnrollment.enroll(user,
                                                                 dest_key,
                                                                 mode=mode)

                        # Un-enroll from the new course if the user had un-enrolled
                        # form the old course.
                        if not old_is_active:
                            new_enrollment.update_enrollment(is_active=False,
                                                             skip_refund=True)
Exemplo n.º 29
0
    def test_enrollment(self):
        user = User.objects.create_user("joe", "*****@*****.**", "password")
        course_id = CourseKey.from_string("edX/Test101/2013")
        course_id_partial = CourseKey.from_string("edX/Test101/")

        # Test basic enrollment
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(
            CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertTrue(
            CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        self.assert_enrollment_event_was_emitted(user, course_id)

        # Enrolling them again should be harmless
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertTrue(
            CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        self.assert_no_events_were_emitted()

        # Now unenroll the user
        CourseEnrollment.unenroll(user, course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(
            CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        self.assert_unenrollment_event_was_emitted(user, course_id)

        # Unenrolling them again should also be harmless
        CourseEnrollment.unenroll(user, course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(
            CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        self.assert_no_events_were_emitted()

        # The enrollment record should still exist, just be inactive
        enrollment_record = CourseEnrollment.objects.get(user=user,
                                                         course_id=course_id)
        self.assertFalse(enrollment_record.is_active)

        # Make sure mode is updated properly if user unenrolls & re-enrolls
        enrollment = CourseEnrollment.enroll(user, course_id, "verified")
        self.assertEqual(enrollment.mode, "verified")
        CourseEnrollment.unenroll(user, course_id)
        enrollment = CourseEnrollment.enroll(user, course_id, "audit")
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertEqual(enrollment.mode, "audit")
Exemplo n.º 30
0
def change_enrollment(request, check_access=True):
    """
    Modify the enrollment status for the logged-in user.

    TODO: This is lms specific and does not belong in common code.

    The request parameter must be a POST request (other methods return 405)
    that specifies course_id and enrollment_action parameters. If course_id or
    enrollment_action is not specified, if course_id is not valid, if
    enrollment_action is something other than "enroll" or "unenroll", if
    enrollment_action is "enroll" and enrollment is closed for the course, or
    if enrollment_action is "unenroll" and the user is not enrolled in the
    course, a 400 error will be returned. If the user is not logged in, 403
    will be returned; it is important that only this case return 403 so the
    front end can redirect the user to a registration or login page when this
    happens. This function should only be called from an AJAX request, so
    the error messages in the responses should never actually be user-visible.

    Args:
        request (`Request`): The Django request object

    Keyword Args:
        check_access (boolean): If True, we check that an accessible course actually
            exists for the given course_key before we enroll the student.
            The default is set to False to avoid breaking legacy code or
            code with non-standard flows (ex. beta tester invitations), but
            for any standard enrollment flow you probably want this to be True.

    Returns:
        Response

    """
    # Get the user
    user = request.user

    # Ensure the user is authenticated
    if not user.is_authenticated:
        return HttpResponseForbidden()

    # Ensure we received a course_id
    action = request.POST.get("enrollment_action")
    if 'course_id' not in request.POST:
        return HttpResponseBadRequest(_("Course id not specified"))

    try:
        course_id = CourseKey.from_string(request.POST.get("course_id"))
    except InvalidKeyError:
        log.warning(
            "User %s tried to %s with invalid course id: %s",
            user.username,
            action,
            request.POST.get("course_id"),
        )
        return HttpResponseBadRequest(_("Invalid course id"))

    # Allow us to monitor performance of this transaction on a per-course basis since we often roll-out features
    # on a per-course basis.
    monitoring_utils.set_custom_attribute('course_id', str(course_id))

    if action == "enroll":
        # Make sure the course exists
        # We don't do this check on unenroll, or a bad course id can't be unenrolled from
        if not modulestore().has_course(course_id):
            log.warning(
                "User %s tried to enroll in non-existent course %s",
                user.username,
                course_id
            )
            return HttpResponseBadRequest(_("Course id is invalid"))

        # Record the user's email opt-in preference
        if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
            _update_email_opt_in(request, course_id.org)

        available_modes = CourseMode.modes_for_course_dict(course_id)

        # Check whether the user is blocked from enrolling in this course
        # This can occur if the user's IP is on a global blacklist
        # or if the user is enrolling in a country in which the course
        # is not available.
        redirect_url = embargo_api.redirect_if_blocked(
            course_id, user=user, ip_address=get_client_ip(request)[0],
            url=request.path
        )
        if redirect_url:
            return HttpResponse(redirect_url)

        if CourseEntitlement.check_for_existing_entitlement_and_enroll(user=user, course_run_key=course_id):
            return HttpResponse(reverse('courseware', args=[str(course_id)]))

        # Check that auto enrollment is allowed for this course
        # (= the course is NOT behind a paywall)
        if CourseMode.can_auto_enroll(course_id):
            # Enroll the user using the default mode (audit)
            # We're assuming that users of the course enrollment table
            # will NOT try to look up the course enrollment model
            # by its slug.  If they do, it's possible (based on the state of the database)
            # for no such model to exist, even though we've set the enrollment type
            # to "audit".
            try:
                enroll_mode = CourseMode.auto_enroll_mode(course_id, available_modes)
                if enroll_mode:
                    CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode)
            except Exception:  # pylint: disable=broad-except
                return HttpResponseBadRequest(_("Could not enroll"))

        # If we have more than one course mode or professional ed is enabled,
        # then send the user to the choose your track page.
        # (In the case of no-id-professional/professional ed, this will redirect to a page that
        # funnels users directly into the verification / payment flow)
        if CourseMode.has_verified_mode(available_modes) or CourseMode.has_professional_mode(available_modes):
            return HttpResponse(
                reverse("course_modes_choose", kwargs={'course_id': str(course_id)})
            )

        # Otherwise, there is only one mode available (the default)
        return HttpResponse()
    elif action == "unenroll":
        enrollment = CourseEnrollment.get_enrollment(user, course_id)
        if not enrollment:
            return HttpResponseBadRequest(_("You are not enrolled in this course"))

        certificate_info = cert_info(user, enrollment.course_overview)
        if certificate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
            return HttpResponseBadRequest(_("Your certificate prevents you from unenrolling from this course"))

        CourseEnrollment.unenroll(user, course_id)
        REFUND_ORDER.send(sender=None, course_enrollment=enrollment)
        return HttpResponse()
    else:
        return HttpResponseBadRequest(_("Enrollment action is invalid"))