Exemplo n.º 1
0
    def test_enrollment_properties_in_segment_traits(self):
        with patch('common.djangoapps.student.models.segment') as mock_segment:
            enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        assert mock_segment.track.call_count == 1
        assert mock_segment.track.call_args[0][
            1] == 'edx.course.enrollment.activated'
        traits = mock_segment.track.call_args[1]['traits']
        assert traits['course_title'] == self.course.display_name
        assert traits['mode'] == 'audit'

        with patch('common.djangoapps.student.models.segment') as mock_segment:
            enrollment.update_enrollment(mode='verified')
        assert mock_segment.track.call_count == 1
        assert mock_segment.track.call_args[0][
            1] == 'edx.course.enrollment.mode_changed'
        traits = mock_segment.track.call_args[1]['traits']
        assert traits['course_title'] == self.course.display_name
        assert traits['mode'] == 'verified'
Exemplo n.º 2
0
    def test_content_gating_holdback(self, put_user_in_holdback, is_gated):
        """
        Test that putting a user in the content gating holdback disables content gating.
        """
        user = UserFactory.create()
        enrollment = CourseEnrollment.enroll(user, self.course.id)
        if put_user_in_holdback:
            FBEEnrollmentExclusion.objects.create(enrollment=enrollment)

        graded, has_score, weight = True, True, 1
        block = self.graded_score_weight_blocks[(graded, has_score, weight)]
        _assert_block_is_gated(
            block=block,
            user=user,
            course=self.course,
            is_gated=is_gated,
            request_factory=self.factory,
        )
Exemplo n.º 3
0
    def setUp(self):  # pylint: disable=arguments-differ
        super().setUp('lms.djangoapps.certificates.utils.tracker')

        self.student = UserFactory.create(email='*****@*****.**',
                                          username='******',
                                          password='******')
        self.student_no_cert = UserFactory()
        self.course = CourseFactory.create(org='edx',
                                           number='verified',
                                           display_name='Verified Course',
                                           grade_cutoffs={
                                               'cutoff': 0.75,
                                               'Pass': 0.5
                                           })
        self.enrollment = CourseEnrollment.enroll(self.student,
                                                  self.course.id,
                                                  mode='honor')
        self.request_factory = RequestFactory()
Exemplo n.º 4
0
    def enroll_user_and_fulfill_entitlement(cls, entitlement, course_run_key):
        """
        Enrolls the user in the Course Run and updates the entitlement with the new Enrollment.

        Returns:
            bool: True if successfully fulfills given entitlement by enrolling the user in the given course run.
        """
        try:
            enrollment = CourseEnrollment.enroll(user=entitlement.user,
                                                 course_key=course_run_key,
                                                 mode=entitlement.mode)
        except CourseEnrollmentException:
            log.exception(
                f'Login for Course Entitlement {entitlement.uuid} failed')
            return False

        entitlement.set_enrollment(enrollment)
        return True
Exemplo n.º 5
0
def create_course_enrollment(username, course_id, mode, is_active):
    """Create a new course enrollment for the given user.

    Creates a new course enrollment for the specified user username.

    Args:
        username (str): The name of the user to create a new course enrollment for.
        course_id (str): The course to create the course enrollment for.
        mode (str): (Optional) The mode for the new enrollment.
        is_active (boolean): (Optional) Determines if the enrollment is active.

    Returns:
        A serializable dictionary representing the new course enrollment.

    Raises:
        CourseNotFoundError
        CourseEnrollmentFullError
        EnrollmentClosedError
        CourseEnrollmentExistsError

    """
    course_key = CourseKey.from_string(course_id)

    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        msg = f"Not user with username '{username}' found."
        log.warning(msg)
        raise UserNotFoundError(msg)  # lint-amnesty, pylint: disable=raise-missing-from

    try:
        enrollment = CourseEnrollment.enroll(user,
                                             course_key,
                                             check_access=True)
        return _update_enrollment(enrollment, is_active=is_active, mode=mode)
    except NonExistentCourseError as err:
        raise CourseNotFoundError(str(err))  # lint-amnesty, pylint: disable=raise-missing-from
    except EnrollmentClosedError as err:
        raise CourseEnrollmentClosedError(str(err))  # lint-amnesty, pylint: disable=raise-missing-from
    except CourseFullError as err:
        raise CourseEnrollmentFullError(str(err))  # lint-amnesty, pylint: disable=raise-missing-from
    except AlreadyEnrolledError as err:
        enrollment = get_course_enrollment(username, course_id)
        raise CourseEnrollmentExistsError(str(err), enrollment)  # lint-amnesty, pylint: disable=raise-missing-from
Exemplo n.º 6
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.º 7
0
    def test_course_mode_info(self):
        verified_mode = CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug='verified',
            mode_display_name='Verified',
            expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1))
        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        course_mode_info = complete_course_mode_info(self.course.id,
                                                     enrollment)
        assert course_mode_info['show_upsell']
        assert course_mode_info['days_for_upsell'] == 1

        verified_mode.expiration_datetime = datetime.now(
            pytz.UTC) + timedelta(days=-1)
        verified_mode.save()
        course_mode_info = complete_course_mode_info(self.course.id,
                                                     enrollment)
        assert not course_mode_info['show_upsell']
        assert course_mode_info['days_for_upsell'] is None
Exemplo n.º 8
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.º 9
0
 def setUp(self):
     super(TestAnalyticsBasic, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.course_key = self.store.make_course_key('robot', 'course', 'id')
     self.users = tuple(UserFactory() for _ in range(30))
     self.ces = tuple(
         CourseEnrollment.enroll(user, self.course_key)
         for user in self.users)
     self.instructor = InstructorFactory(course_key=self.course_key)
     for user in self.users:
         user.profile.meta = json.dumps({
             "position":
             u"edX expert {}".format(user.id),
             "company":
             u"Open edX Inc {}".format(user.id),
         })
         user.profile.save()
     self.students_who_may_enroll = list(
         self.users) + [UserFactory() for _ in range(5)]
     for student in self.students_who_may_enroll:
         CourseEnrollmentAllowed.objects.create(email=student.email,
                                                course_id=self.course_key)
    def setUp(self):
        super().setUp()
        self.courses = []

        self.user_info = [
            ('amy', '*****@*****.**', 'password'),
            ('rory', '*****@*****.**', 'password'),
            ('river', '*****@*****.**', 'password')
        ]

        self.enrollments = []
        self.users = []

        for username, email, password in self.user_info:
            user = UserFactory.create(username=username, email=email, password=password)
            self.users.append(user)
            course = CourseFactory.create()
            CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.AUDIT)
            CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.VERIFIED)
            self.courses.append(course)
            self.enrollments.append(CourseEnrollment.enroll(user, course.id, mode=CourseMode.AUDIT))
 def setUp(self):
     super().setUp()
     self.course_key = self.store.make_course_key('robot', 'course', 'id')
     self.users = tuple(UserFactory() for _ in range(30))
     self.ces = tuple(
         CourseEnrollment.enroll(user, self.course_key)
         for user in self.users)
     self.instructor = InstructorFactory(course_key=self.course_key)
     for user in self.users:
         user.profile.meta = json.dumps({
             "position":
             f"edX expert {user.id}",
             "company":
             f"Open edX Inc {user.id}",
         })
         user.profile.save()
     self.students_who_may_enroll = list(
         self.users) + [UserFactory() for _ in range(5)]
     for student in self.students_who_may_enroll:
         CourseEnrollmentAllowed.objects.create(email=student.email,
                                                course_id=self.course_key)
    def setUp(self):
        super().setUp()
        self.course = CourseFactory.create(org='Robot',
                                           number='999',
                                           display_name='Test Course')

        self.user = UserFactory()
        self.request = RequestFactory().get('/')
        self.request.user = self.user
        self.client.login(username=self.user.username,
                          password=self.user.password)

        # Enable the course for credit
        CreditCourse.objects.create(
            course_key=self.course.id,
            enabled=True,
        )

        # Configure a credit provider for the course
        CreditProvider.objects.create(
            provider_id="ASU",
            enable_integration=True,
            provider_url="https://credit.example.com/request",
        )

        requirements = [{
            "namespace": "grade",
            "name": "grade",
            "display_name": "Grade",
            "criteria": {
                "min_grade": 0.52
            },
        }]
        # Add a single credit requirement (final grade)
        set_credit_requirements(self.course.id, requirements)

        # Enroll user in verified mode.
        self.enrollment = CourseEnrollment.enroll(self.user,
                                                  self.course.id,
                                                  mode=CourseMode.VERIFIED)
Exemplo n.º 13
0
    def _enroll_entitlement(self, entitlement, course_run_key, user):
        """
        Internal method to handle the details of enrolling a User in a Course Run.

        Returns a response object is there is an error or exception, None otherwise
        """
        try:
            unexpired_paid_modes = [
                mode.slug
                for mode in CourseMode.paid_modes_for_course(course_run_key)
            ]
            can_upgrade = unexpired_paid_modes and entitlement.mode in unexpired_paid_modes
            enrollment = CourseEnrollment.enroll(user=user,
                                                 course_key=course_run_key,
                                                 mode=entitlement.mode,
                                                 check_access=True,
                                                 can_upgrade=can_upgrade)
        except AlreadyEnrolledError:
            enrollment = CourseEnrollment.get_enrollment(user, course_run_key)
            if enrollment.mode == entitlement.mode:
                entitlement.set_enrollment(enrollment)
            elif enrollment.mode not in unexpired_paid_modes:
                enrollment.update_enrollment(mode=entitlement.mode)
                entitlement.set_enrollment(enrollment)
            # Else the User is already enrolled in another paid Mode and we should
            # not do anything else related to Entitlements.
        except CourseEnrollmentException:
            message = (
                'Course Entitlement Enroll for {username} failed for course: {course_id}, '
                'mode: {mode}, and entitlement: {entitlement}').format(
                    username=user.username,
                    course_id=course_run_key,
                    mode=entitlement.mode,
                    entitlement=entitlement.uuid)
            return Response(status=status.HTTP_400_BAD_REQUEST,
                            data={'message': message})

        entitlement.set_enrollment(enrollment)
        return None
Exemplo n.º 14
0
    def test_expired_course(self):
        """
        Ensure that a user accessing an expired course sees a redirect to
        the student dashboard, not a 404.
        """
        CourseDurationLimitConfig.objects.create(enabled=True,
                                                 enabled_as_of=datetime(
                                                     2010, 1, 1, tzinfo=UTC))
        course = CourseFactory.create(start=THREE_YEARS_AGO)
        url = course_home_url(course)

        for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]:
            CourseModeFactory.create(course_id=course.id, mode_slug=mode)

        # assert that an if an expired audit user tries to access the course they are redirected to the dashboard
        audit_user = UserFactory(password=self.TEST_PASSWORD)
        self.client.login(username=audit_user.username,
                          password=self.TEST_PASSWORD)
        audit_enrollment = CourseEnrollment.enroll(audit_user,
                                                   course.id,
                                                   mode=CourseMode.AUDIT)
        audit_enrollment.created = THREE_YEARS_AGO + timedelta(days=1)
        audit_enrollment.save()
        ScheduleFactory(enrollment=audit_enrollment)

        response = self.client.get(url)

        expiration_date = strftime_localized(
            course.start + timedelta(weeks=4) + timedelta(days=1),
            u'%b %-d, %Y')
        expected_params = QueryDict(mutable=True)
        course_name = CourseOverview.get_from_id(
            course.id).display_name_with_default
        expected_params[
            'access_response_error'] = u'Access to {run} expired on {expiration_date}'.format(
                run=course_name, expiration_date=expiration_date)
        expected_url = '{url}?{params}'.format(
            url=reverse('dashboard'), params=expected_params.urlencode())
        self.assertRedirects(response, expected_url)
Exemplo n.º 15
0
    def setUp(self):
        super(BulkUnenrollTests, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
        self.course = CourseFactory.create()
        self.audit_mode = CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug='audit',
            mode_display_name='Audit',
        )

        self.user_info = [('amy', '*****@*****.**', 'password'),
                          ('rory', '*****@*****.**', 'password'),
                          ('river', '*****@*****.**', 'password')]
        self.enrollments = []
        self.users = []

        for username, email, password in self.user_info:
            user = UserFactory.create(username=username,
                                      email=email,
                                      password=password)
            self.users.append(user)
            self.enrollments.append(
                CourseEnrollment.enroll(user, self.course.id, mode='audit'))
Exemplo n.º 16
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.º 17
0
    def test_course_goal_updates(self):
        """
        Ensure that the following five use cases work as expected.

        1) Unenrolled users are not shown the update goal selection field.
        2) Enrolled users are not shown the update goal selection field if they have not yet set a course goal.
        3) Enrolled users are shown the update goal selection field if they have set a course goal.
        4) Enrolled users in the verified track are shown the update goal selection field.
        """
        # Create a course with a verified track.
        verifiable_course = CourseFactory.create()
        add_course_mode(verifiable_course, upgrade_deadline_expired=False)

        # Verify that unenrolled users are not shown the update goal selection field.
        user = self.create_user_for_course(verifiable_course,
                                           CourseUserType.UNENROLLED)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertNotContains(response, TEST_COURSE_GOAL_UPDATE_FIELD)

        # Verify that enrolled users that have not set a course goal are shown a hidden update goal selection field.
        enrollment = CourseEnrollment.enroll(user, verifiable_course.id)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertContains(response, TEST_COURSE_GOAL_UPDATE_FIELD_HIDDEN)

        # Verify that enrolled users that have set a course goal are shown a visible update goal selection field.
        add_course_goal_deprecated(user, verifiable_course.id,
                                   COURSE_GOAL_DISMISS_OPTION)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertContains(response, TEST_COURSE_GOAL_UPDATE_FIELD)
        self.assertNotContains(response, TEST_COURSE_GOAL_UPDATE_FIELD_HIDDEN)

        # Verify that enrolled and verified users are shown the update goal selection
        CourseEnrollment.update_enrollment(enrollment,
                                           is_active=True,
                                           mode=CourseMode.VERIFIED)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertContains(response, TEST_COURSE_GOAL_UPDATE_FIELD)
        self.assertNotContains(response, TEST_COURSE_GOAL_UPDATE_FIELD_HIDDEN)
    def test_expired_upgrade_deadline(self, mock_get_course_run_details):
        """
        The expiration date still exists if the upgrade deadline has passed
        """
        access_duration = timedelta(weeks=7)
        mock_get_course_run_details.return_value = {'weeks_to_complete': 7}

        start_date = now() - timedelta(weeks=10)
        course = CourseFactory(start=start_date)
        enrollment = CourseEnrollment.enroll(self.user, course.id, CourseMode.AUDIT)
        CourseDurationLimitConfig.objects.create(
            enabled=True,
            course=CourseOverview.get_from_id(course.id),
            enabled_as_of=course.start,
        )
        add_course_mode(course, mode_slug=CourseMode.AUDIT)
        add_course_mode(course, upgrade_deadline_expired=True)
        result = get_user_course_expiration_date(
            self.user,
            CourseOverview.get_from_id(course.id),
        )
        content_availability_date = enrollment.created
        assert result == (content_availability_date + access_duration)
Exemplo n.º 19
0
    def test_add_entitlement_inactive_audit_enrollment(self, mock_get_course_runs):
        """
        Verify that if an entitlement is added for a user, if the user has an inactive audit enrollment
        that enrollment is NOT upgraded to the mode of the entitlement and linked to the entitlement.
        """
        course_uuid = uuid.uuid4()
        entitlement_data = self._get_data_set(self.user, str(course_uuid))
        mock_get_course_runs.return_value = [{'key': str(self.course.id)}]  # pylint: disable=no-member

        # Add an audit course enrollment for user.
        enrollment = CourseEnrollment.enroll(
            self.user,
            self.course.id,  # pylint: disable=no-member
            mode=CourseMode.AUDIT
        )
        enrollment.update_enrollment(is_active=False)
        response = self.client.post(
            self.entitlements_list_url,
            data=json.dumps(entitlement_data),
            content_type='application/json',
        )
        assert response.status_code == 201
        results = response.data

        course_entitlement = CourseEntitlement.objects.get(
            user=self.user,
            course_uuid=course_uuid
        )
        # Assert that enrollment mode is now verified
        enrollment_mode, enrollment_active = CourseEnrollment.enrollment_mode_for_user(
            self.user,
            self.course.id  # pylint: disable=no-member
        )
        assert enrollment_mode == CourseMode.AUDIT
        assert enrollment_active is False
        assert course_entitlement.enrollment_course_run is None
        assert results == CourseEntitlementSerializer(course_entitlement).data
Exemplo n.º 20
0
    def test_course_goals(self):
        """
        Ensure that the following five use cases work as expected.

        1) Unenrolled users are not shown the set course goal message.
        2) Enrolled users are shown the set course goal message if they have not yet set a course goal.
        3) Enrolled users are not shown the set course goal message if they have set a course goal.
        4) Enrolled and verified users are not shown the set course goal message.
        5) Enrolled users are not shown the set course goal message in a course that cannot be verified.
        """
        # Create a course with a verified track.
        verifiable_course = CourseFactory.create()
        add_course_mode(verifiable_course, upgrade_deadline_expired=False)

        # Verify that unenrolled users are not shown the set course goal message.
        user = self.create_user_for_course(verifiable_course,
                                           CourseUserType.UNENROLLED)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)

        # Verify that enrolled users are shown the set course goal message in a verified course.
        CourseEnrollment.enroll(user, verifiable_course.id)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertContains(response, TEST_COURSE_GOAL_OPTIONS)

        # Verify that enrolled users that have set a course goal are not shown the set course goal message.
        add_course_goal_deprecated(user, verifiable_course.id,
                                   COURSE_GOAL_DISMISS_OPTION)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)

        # Verify that enrolled and verified users are not shown the set course goal message.
        get_course_goal(user, verifiable_course.id).delete()
        CourseEnrollment.enroll(user, verifiable_course.id,
                                CourseMode.VERIFIED)
        response = self.client.get(course_home_url(verifiable_course))
        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)

        # Verify that enrolled users are not shown the set course goal message in an audit only course.
        audit_only_course = CourseFactory.create()
        CourseEnrollment.enroll(user, audit_only_course.id)
        response = self.client.get(course_home_url(audit_only_course))
        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)
    def setUp(self):
        """
        Test case scaffolding
        """
        super().setUp()
        self.course = CourseFactory.create(
            metadata={
                'entrance_exam_enabled': True,
            }
        )
        with self.store.bulk_operations(self.course.id):
            self.chapter = ItemFactory.create(
                parent=self.course,
                display_name='Overview'
            )
            self.welcome = ItemFactory.create(
                parent=self.chapter,
                display_name='Welcome'
            )
            ItemFactory.create(
                parent=self.course,
                category='chapter',
                display_name="Week 1"
            )
            self.chapter_subsection = ItemFactory.create(
                parent=self.chapter,
                category='sequential',
                display_name="Lesson 1"
            )
            chapter_vertical = ItemFactory.create(
                parent=self.chapter_subsection,
                category='vertical',
                display_name='Lesson 1 Vertical - Unit 1'
            )
            ItemFactory.create(
                parent=chapter_vertical,
                category="problem",
                display_name="Problem - Unit 1 Problem 1"
            )
            ItemFactory.create(
                parent=chapter_vertical,
                category="problem",
                display_name="Problem - Unit 1 Problem 2"
            )

            ItemFactory.create(
                category="instructor",
                parent=self.course,
                data="Instructor Tab",
                display_name="Instructor"
            )
            self.entrance_exam = ItemFactory.create(
                parent=self.course,
                category="chapter",
                display_name="Entrance Exam Section - Chapter 1",
                is_entrance_exam=True,
                in_entrance_exam=True
            )
            self.exam_1 = ItemFactory.create(
                parent=self.entrance_exam,
                category='sequential',
                display_name="Exam Sequential - Subsection 1",
                graded=True,
                in_entrance_exam=True
            )
            subsection = ItemFactory.create(
                parent=self.exam_1,
                category='vertical',
                display_name='Exam Vertical - Unit 1'
            )
            problem_xml = MultipleChoiceResponseXMLFactory().build_xml(
                question_text='The correct answer is Choice 3',
                choices=[False, False, True, False],
                choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
            )
            self.problem_1 = ItemFactory.create(
                parent=subsection,
                category="problem",
                display_name="Exam Problem - Problem 1",
                data=problem_xml
            )
            self.problem_2 = ItemFactory.create(
                parent=subsection,
                category="problem",
                display_name="Exam Problem - Problem 2"
            )

        add_entrance_exam_milestone(self.course, self.entrance_exam)

        self.course.entrance_exam_enabled = True
        self.course.entrance_exam_minimum_score_pct = 0.50
        self.course.entrance_exam_id = str(self.entrance_exam.scope_ids.usage_id)

        self.anonymous_user = AnonymousUserFactory()
        self.addCleanup(set_current_request, None)
        self.request = get_mock_request(UserFactory())
        modulestore().update_item(self.course, self.request.user.id)

        self.client.login(username=self.request.user.username, password="******")
        CourseEnrollment.enroll(self.request.user, self.course.id)

        self.expected_locked_toc = (
            [
                {
                    'active': True,
                    'sections': [
                        {
                            'url_name': 'Exam_Sequential_-_Subsection_1',
                            'display_name': 'Exam Sequential - Subsection 1',
                            'graded': True,
                            'format': '',
                            'due': None,
                            'active': True
                        }
                    ],
                    'url_name': 'Entrance_Exam_Section_-_Chapter_1',
                    'display_name': 'Entrance Exam Section - Chapter 1',
                    'display_id': 'entrance-exam-section-chapter-1',
                }
            ]
        )
        self.expected_unlocked_toc = (
            [
                {
                    'active': False,
                    'sections': [
                        {
                            'url_name': 'Welcome',
                            'display_name': 'Welcome',
                            'graded': False,
                            'format': '',
                            'due': None,
                            'active': False
                        },
                        {
                            'url_name': 'Lesson_1',
                            'display_name': 'Lesson 1',
                            'graded': False,
                            'format': '',
                            'due': None,
                            'active': False
                        }
                    ],
                    'url_name': 'Overview',
                    'display_name': 'Overview',
                    'display_id': 'overview'
                },
                {
                    'active': False,
                    'sections': [],
                    'url_name': 'Week_1',
                    'display_name': 'Week 1',
                    'display_id': 'week-1'
                },
                {
                    'active': False,
                    'sections': [],
                    'url_name': 'Instructor',
                    'display_name': 'Instructor',
                    'display_id': 'instructor'
                },
                {
                    'active': True,
                    'sections': [
                        {
                            'url_name': 'Exam_Sequential_-_Subsection_1',
                            'display_name': 'Exam Sequential - Subsection 1',
                            'graded': True,
                            'format': '',
                            'due': None,
                            'active': True
                        }
                    ],
                    'url_name': 'Entrance_Exam_Section_-_Chapter_1',
                    'display_name': 'Entrance Exam Section - Chapter 1',
                    'display_id': 'entrance-exam-section-chapter-1'
                }
            ]
        )
Exemplo n.º 22
0
 def _setup_user(self):
     self.user = UserFactory.create()
     CourseEnrollment.enroll(self.user, self.course_key)
Exemplo n.º 23
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"))
Exemplo n.º 24
0
 def enroll(self, course_id=None):
     """Enroll test user in test course."""
     CourseEnrollment.enroll(self.user, course_id or self.course.id)
Exemplo n.º 25
0
    def post(self, request, course_id):
        """Takes the form submission from the page and parses it.

        Args:
            request (`Request`): The Django Request object.
            course_id (unicode): The slash-separated course key.

        Returns:
            Status code 400 when the requested mode is unsupported. When the honor mode
            is selected, redirects to the dashboard. When the verified mode is selected,
            returns error messages if the indicated contribution amount is invalid or
            below the minimum, otherwise redirects to the verification flow.

        """
        course_key = CourseKey.from_string(course_id)
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollment,
        # but I don't really have the time to refactor it more nicely and test.
        course = modulestore().get_course(course_key)
        if not user.has_perm(ENROLL_IN_COURSE, course):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        requested_mode = self._get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_key)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode == 'audit':
            # If the learner has arrived at this screen via the traditional enrollment workflow,
            # then they should already be enrolled in an audit mode for the course, assuming one has
            # been configured.  However, alternative enrollment workflows have been introduced into the
            # system, such as third-party discovery.  These workflows result in learners arriving
            # directly at this screen, and they will not necessarily be pre-enrolled in the audit mode.
            CourseEnrollment.enroll(request.user, course_key, CourseMode.AUDIT)
            return self._redirect_to_course_or_dashboard(
                course, course_key, user)

        if requested_mode == 'honor':
            CourseEnrollment.enroll(user, course_key, mode=requested_mode)
            return self._redirect_to_course_or_dashboard(
                course, course_key, user)

        mode_info = allowed_modes[requested_mode]

        if requested_mode == 'verified':
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # Validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(
                    decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _(
                    "No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course",
                                                      {})
            donation_for_course[str(course_key)] = amount_value
            request.session["donation_for_course"] = donation_for_course

            verify_url = IDVerificationService.get_verify_location(
                course_id=course_key)
            return redirect(verify_url)
Exemplo n.º 26
0
 def setUp(self):
     super().setUp()
     self.enrollment = CourseEnrollment.enroll(self.user, self.course.id,
                                               'verified')
Exemplo n.º 27
0
 def test_waffle_flag_disabled(self, enrollment_mode):
     CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
     response = self.client.get(self.url)
     self.assertEqual(response.status_code, 404)
Exemplo n.º 28
0
    def test_transfer_students(self):
        """
        Verify the transfer student command works as intended.
        """
        student = UserFactory.create()
        student.set_password(self.PASSWORD)
        student.save()
        mode = 'verified'
        # Original Course
        original_course_location = locator.CourseLocator(
            'Org0', 'Course0', 'Run0')
        course = self._create_course(original_course_location)
        # Enroll the student in 'verified'
        CourseEnrollment.enroll(student, course.id, mode='verified')

        # Create and purchase a verified cert for the original course.
        self._create_and_purchase_verified(student, course.id)

        # New Course 1
        course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1')
        new_course_one = self._create_course(course_location_one)

        # New Course 2
        course_location_two = locator.CourseLocator('Org2', 'Course2', 'Run2')
        new_course_two = self._create_course(course_location_two)
        original_key = text_type(course.id)
        new_key_one = text_type(new_course_one.id)
        new_key_two = text_type(new_course_two.id)

        # Run the actual management command
        call_command(
            'transfer_students',
            '--from',
            original_key,
            '--to',
            new_key_one,
            new_key_two,
        )
        self.assertTrue(self.signal_fired)

        # Confirm the analytics event was emitted.
        self.mock_tracker.emit.assert_has_calls([
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_DEACTIVATED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': new_key_one,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': new_key_one,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': new_key_two,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': new_key_two,
                'user_id': student.id,
                'mode': mode
            })
        ])
        self.mock_tracker.reset_mock()

        # Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
        self.assertEqual(
            (mode, False),
            CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEqual(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_one.id))
        self.assertEqual(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_two.id))
 def test_enrollment_mode(self):
     """Tests that verified enrollments do not have an expiration"""
     CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
     result = get_user_course_expiration_date(
         self.user, CourseOverview.get_from_id(self.course.id))
     assert result is None
Exemplo n.º 30
0
    def test_course_metadata(self, logged_in, enrollment_mode,
                             enable_anonymous,
                             is_microfrontend_enabled_for_user):
        is_microfrontend_enabled_for_user.return_value = True
        check_public_access = mock.Mock()
        check_public_access.return_value = enable_anonymous
        with mock.patch(
                'lms.djangoapps.courseware.access_utils.check_public_access',
                check_public_access):
            if not logged_in:
                self.client.logout()
            if enrollment_mode == 'verified':
                cert = GeneratedCertificateFactory.create(
                    user=self.user,
                    course_id=self.course.id,
                    status='downloadable',
                    mode='verified',
                )
            if enrollment_mode:
                CourseEnrollment.enroll(self.user, self.course.id,
                                        enrollment_mode)

            response = self.client.get(self.url)
            assert response.status_code == 200
            if enrollment_mode:
                enrollment = response.data['enrollment']
                assert enrollment_mode == enrollment['mode']
                assert enrollment['is_active']
                assert len(response.data['tabs']) == 6
                found = False
                for tab in response.data['tabs']:
                    if tab['type'] == 'external_link':
                        assert tab[
                            'url'] != 'http://hidden.com', "Hidden tab is not hidden"
                        if tab['url'] == 'http://zombo.com':
                            found = True
                assert found, 'external link not in course tabs'

                assert not response.data['user_has_passing_grade']
                if enrollment_mode == 'audit':
                    assert response.data['verify_identity_url'] is None
                    assert response.data['verification_status'] == 'none'  # lint-amnesty, pylint: disable=literal-comparison
                    assert response.data['linkedin_add_to_profile_url'] is None
                else:
                    assert response.data['certificate_data'][
                        'cert_status'] == 'earned_but_not_available'
                    expected_verify_identity_url = IDVerificationService.get_verify_location(
                        course_id=self.course.id)
                    # The response contains an absolute URL so this is only checking the path of the final
                    assert expected_verify_identity_url in response.data[
                        'verify_identity_url']
                    assert response.data['verification_status'] == 'none'  # lint-amnesty, pylint: disable=literal-comparison

                    request = RequestFactory().request()
                    cert_url = get_certificate_url(course_id=self.course.id,
                                                   uuid=cert.verify_uuid)
                    linkedin_url_params = {
                        'name':
                        '{platform_name} Verified Certificate for {course_name}'
                        .format(
                            platform_name=settings.PLATFORM_NAME,
                            course_name=self.course.display_name,
                        ),
                        'certUrl':
                        request.build_absolute_uri(cert_url),
                        # default value from the LinkedInAddToProfileConfigurationFactory company_identifier
                        'organizationId':
                        1337,
                        'certId':
                        cert.verify_uuid,
                        'issueYear':
                        cert.created_date.year,
                        'issueMonth':
                        cert.created_date.month,
                    }
                    expected_linkedin_url = (
                        'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&{params}'
                        .format(params=urlencode(linkedin_url_params)))
                    assert response.data[
                        'linkedin_add_to_profile_url'] == expected_linkedin_url
            elif enable_anonymous and not logged_in:
                # multiple checks use this handler
                check_public_access.assert_called()
                assert response.data['enrollment']['mode'] is None
                assert response.data['course_access']['has_access']
            else:
                assert not response.data['course_access']['has_access']