Ejemplo n.º 1
0
    def test_downgrade_enrollment_with_mode(self):
        """With the right API key, downgrade an existing enrollment with a new mode. """
        # Create an honor and verified mode for a course. This allows an update.
        for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]:
            CourseModeFactory.create(
                course_id=self.course.id,
                mode_slug=mode,
                mode_display_name=mode,
            )

        # Create a 'verified' enrollment
        self.assert_enrollment_status(as_server=True, mode=CourseMode.VERIFIED)

        # Check that the enrollment is verified.
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
        course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertTrue(is_active)
        self.assertEqual(course_mode, CourseMode.VERIFIED)

        # Check that the enrollment was downgraded to the default mode.
        self.assert_enrollment_status(
            as_server=True,
            mode=CourseMode.DEFAULT_MODE_SLUG,
            expected_status=status.HTTP_200_OK
        )
        course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertTrue(is_active)
        self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG)
Ejemplo n.º 2
0
    def test_dashboard_metadata_caching(self, modulestore_type):
        """
        Check that the student dashboard makes use of course metadata caching.

        After creating a course, that course's metadata should be cached as a
        CourseOverview. The student dashboard should never have to make calls to
        the modulestore.

        Arguments:
            modulestore_type (ModuleStoreEnum.Type): Type of modulestore to create
                test course in.

        Note to future developers:
            If you break this test so that the "check_mongo_calls(0)" fails,
            please do NOT change it to "check_mongo_calls(n>1)". Instead, change
            your code to not load courses from the module store. This may
            involve adding fields to CourseOverview so that loading a full
            CourseDescriptor isn't necessary.
        """
        # Create a course and log in the user.
        # Creating a new course will trigger a publish event and the course will be cached
        test_course = CourseFactory.create(default_store=modulestore_type, emit_signals=True)
        self.client.login(username="******", password="******")

        with check_mongo_calls(0):
            CourseEnrollment.enroll(self.user, test_course.id)

        # Subsequent requests will only result in SQL queries to load the
        # CourseOverview object that has been created.
        with check_mongo_calls(0):
            response_1 = self.client.get(reverse('dashboard'))
            self.assertEquals(response_1.status_code, 200)
            response_2 = self.client.get(reverse('dashboard'))
            self.assertEquals(response_2.status_code, 200)
Ejemplo n.º 3
0
 def test_roundtrip_with_unicode_course_id(self):
     course2 = CourseFactory.create(display_name=u"Omega Course Ω")
     CourseEnrollment.enroll(self.user, course2.id)
     anonymous_id = anonymous_id_for_user(self.user, course2.id)
     real_user = user_by_anonymous_id(anonymous_id)
     self.assertEqual(self.user, real_user)
     self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, course2.id, save=False))
Ejemplo n.º 4
0
    def test_update_enrollment_with_expired_mode_throws_error(self):
        """Verify that if verified mode is expired than it's enrollment cannot be updated. """
        for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]:
            CourseModeFactory.create(
                course_id=self.course.id,
                mode_slug=mode,
                mode_display_name=mode,
            )

        # Create an enrollment
        self.assert_enrollment_status(as_server=True)

        # Check that the enrollment is the default.
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
        course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertTrue(is_active)
        self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG)

        # Change verified mode expiration.
        mode = CourseMode.objects.get(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
        mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=pytz.utc)
        mode.save()
        self.assert_enrollment_status(
            as_server=True,
            mode=CourseMode.VERIFIED,
            expected_status=status.HTTP_400_BAD_REQUEST
        )
        course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertTrue(is_active)
        self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG)
Ejemplo n.º 5
0
    def test_linked_in_add_to_profile_btn_not_appearing_without_config(self):
        # Without linked-in config don't show Add Certificate to LinkedIn button
        self.client.login(username="******", password="******")

        CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug='verified',
            mode_display_name='verified',
            expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1)
        )

        CourseEnrollment.enroll(self.user, self.course.id, mode='honor')

        self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
        self.course.end = datetime.now(pytz.UTC) - timedelta(days=1)
        self.course.display_name = u"Omega"
        self.course = self.update_course(self.course, self.user.id)

        download_url = 'www.edx.org'
        GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            mode='honor',
            grade='67',
            download_url=download_url
        )
        response = self.client.get(reverse('dashboard'))

        self.assertEquals(response.status_code, 200)
        self.assertNotIn('Add Certificate to LinkedIn', response.content)

        response_url = 'http://www.linkedin.com/profile/add?_ed='
        self.assertNotContains(response, escape(response_url))
Ejemplo n.º 6
0
def unenroll_email(course_id, student_email, email_students=False, email_params=None):
    """
    Unenroll a student by email.

    `student_email` is student's emails e.g. "*****@*****.**"
    `email_students` determines if student should be notified of action by email.
    `email_params` parameters used while parsing email templates (a `dict`).

    returns two EmailEnrollmentState's
        representing state before and after the action.
    """

    previous_state = EmailEnrollmentState(course_id, student_email)

    if previous_state.enrollment:
        CourseEnrollment.unenroll_by_email(student_email, course_id)
        if email_students:
            email_params['message'] = 'enrolled_unenroll'
            email_params['email_address'] = student_email
            email_params['full_name'] = previous_state.full_name
            send_mail_to_student(student_email, email_params)

    if previous_state.allowed:
        CourseEnrollmentAllowed.objects.get(course_id=course_id, email=student_email).delete()
        if email_students:
            email_params['message'] = 'allowed_unenroll'
            email_params['email_address'] = student_email
            # Since no User object exists for this student there is no "full_name" available.
            send_mail_to_student(student_email, email_params)

    after_state = EmailEnrollmentState(course_id, student_email)

    return previous_state, after_state
Ejemplo n.º 7
0
    def test_enroll(self, course_modes, next_url, enrollment_mode):
        # Create the course modes (if any) required for this test case
        for mode_slug in course_modes:
            CourseModeFactory.create(
                course_id=self.course.id,
                mode_slug=mode_slug,
                mode_display_name=mode_slug,
            )

        # Reverse the expected next URL, if one is provided
        # (otherwise, use an empty string, which the JavaScript client
        # interprets as a redirect to the dashboard)
        full_url = (
            reverse(next_url, kwargs={'course_id': unicode(self.course.id)})
            if next_url else next_url
        )

        # Enroll in the course and verify the URL we get sent to
        resp = self._change_enrollment('enroll')
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.content, full_url)

        # If we're not expecting to be enrolled, verify that this is the case
        if enrollment_mode is None:
            self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))

        # Otherwise, verify that we're enrolled with the expected course mode
        else:
            self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
            course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
            self.assertTrue(is_active)
            self.assertEqual(course_mode, enrollment_mode)
Ejemplo n.º 8
0
    def test_ssl_cms_redirection(self):
        """
        Auto signup auth user and ensure they return to the original
        url they visited after being logged in.
        """
        course = CourseFactory.create(
            org='MITx',
            number='999',
            display_name='Robot Super Course'
        )

        external_auth.views.ssl_login(self._create_ssl_request('/'))
        user = User.objects.get(email=self.USER_EMAIL)
        CourseEnrollment.enroll(user, course.id)

        CourseStaffRole(course.id).add_users(user)
        course_private_url = reverse('course_handler', args=(unicode(course.id),))
        self.assertFalse(SESSION_KEY in self.client.session)

        response = self.client.get(
            course_private_url,
            follow=True,
            SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL),
            HTTP_ACCEPT='text/html'
        )
        self.assertEqual(('http://testserver{0}'.format(course_private_url), 302),
                         response.redirect_chain[-1])
        self.assertIn(SESSION_KEY, self.client.session)
Ejemplo n.º 9
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)
        )
Ejemplo n.º 10
0
    def test_enrolled_students_features_keys_cohorted(self):
        course = CourseFactory.create(org="test", course="course1", display_name="run1")
        course.cohort_config = {'cohorted': True, 'auto_cohort': True, 'auto_cohort_groups': ['cohort']}
        self.store.update_item(course, self.instructor.id)
        cohorted_students = [UserFactory.create() for _ in xrange(10)]
        cohort = CohortFactory.create(name='cohort', course_id=course.id, users=cohorted_students)
        cohorted_usernames = [student.username for student in cohorted_students]
        non_cohorted_student = UserFactory.create()
        for student in cohorted_students:
            cohort.users.add(student)
            CourseEnrollment.enroll(student, course.id)
        CourseEnrollment.enroll(non_cohorted_student, course.id)
        instructor = InstructorFactory(course_key=course.id)
        self.client.login(username=instructor.username, password='******')

        query_features = ('username', 'cohort')
        # There should be a constant of 2 SQL queries when calling
        # enrolled_students_features.  The first query comes from the call to
        # User.objects.filter(...), and the second comes from
        # prefetch_related('course_groups').
        with self.assertNumQueries(2):
            userreports = enrolled_students_features(course.id, query_features)
        self.assertEqual(len([r for r in userreports if r['username'] in cohorted_usernames]), len(cohorted_students))
        self.assertEqual(len([r for r in userreports if r['username'] == non_cohorted_student.username]), 1)
        for report in userreports:
            self.assertEqual(set(report.keys()), set(query_features))
            if report['username'] in cohorted_usernames:
                self.assertEqual(report['cohort'], cohort.name)
            else:
                self.assertEqual(report['cohort'], '[unassigned]')
Ejemplo n.º 11
0
    def test_transfer_students(self):
        student = UserFactory()
        student.set_password(self.PASSWORD)  # pylint: disable=E1101
        student.save()   # pylint: disable=E1101

        # 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")

        # 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 = unicode(course.id)
        new_key_one = unicode(new_course_one.id)
        new_key_two = unicode(new_course_two.id)

        # Run the actual management command
        transfer_students.Command().handle(
            source_course=original_key, dest_course_list=new_key_one + "," + new_key_two
        )

        # Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
        self.assertEquals(('verified', False), CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_one.id))
        self.assertEquals(('verified', True), CourseEnrollment.enrollment_mode_for_user(student, new_course_two.id))
Ejemplo n.º 12
0
    def test_certificate_exception_user_not_enrolled_error(self):
        """
        Test certificates exception addition api endpoint returns failure when called with
        username/email that is not enrolled in the given course.
        """
        # Un-enroll student from the course
        CourseEnrollment.unenroll(self.user, self.course.id)
        response = self.client.post(
            self.url,
            data=json.dumps(self.certificate_exception),
            content_type='application/json'
        )

        # Assert 400 status code in response
        self.assertEqual(response.status_code, 400)
        res_json = json.loads(response.content)

        # Assert Request not successful
        self.assertFalse(res_json['success'])

        # Assert Error Message
        self.assertEqual(
            res_json['message'],
            "{user} is not enrolled in this course. Please check your spelling and retry.".format(
                user=self.certificate_exception['user_name']
            )
        )
Ejemplo n.º 13
0
    def setUp(self):
        super(TestCertificatesInstructorApiBulkWhiteListExceptions, self).setUp()
        self.global_staff = GlobalStaffFactory()
        self.enrolled_user_1 = UserFactory(
            username='******',
            email='*****@*****.**',
            first_name='Enrolled',
            last_name='Student'
        )
        self.enrolled_user_2 = UserFactory(
            username='******',
            email='*****@*****.**',
            first_name='Enrolled',
            last_name='Student'
        )

        self.not_enrolled_student = UserFactory(
            username='******',
            email='*****@*****.**',
            first_name='NotEnrolled',
            last_name='Student'
        )
        CourseEnrollment.enroll(self.enrolled_user_1, self.course.id)
        CourseEnrollment.enroll(self.enrolled_user_2, self.course.id)

        # Global staff can see the certificates section
        self.client.login(username=self.global_staff.username, password="******")
Ejemplo n.º 14
0
    def handle(self, *args, **options):
        csv_path = options['csv_path']
        with open(csv_path) as csvfile:
            reader = unicodecsv.DictReader(csvfile)
            for row in reader:
                username = row['username']
                email = row['email']
                course_key = row['course_id']
                try:
                    user = User.objects.get(Q(username=username) | Q(email=email))
                except ObjectDoesNotExist:
                    user = None
                    msg = 'User with username {} or email {} does not exist'.format(username, email)
                    logger.warning(msg)

                try:
                    course_id = CourseKey.from_string(course_key)
                except InvalidKeyError:
                    course_id = None
                    msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row)
                    logger.warning(msg)

                if user and course_id:
                    enrollment = CourseEnrollment.get_enrollment(user, course_id)
                    if not enrollment:
                        msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key)
                        logger.info(msg)
                    else:
                        try:
                            CourseEnrollment.unenroll(user, course_id, skip_refund=True)
                        except Exception as err:
                            msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err)
                            logger.error(msg, exc_info=True)
Ejemplo n.º 15
0
    def setUp(self):
        super(CertificateExceptionViewInstructorApiTest, self).setUp()
        self.global_staff = GlobalStaffFactory()
        self.instructor = InstructorFactory(course_key=self.course.id)
        self.user = UserFactory()
        self.user2 = UserFactory()
        CourseEnrollment.enroll(self.user, self.course.id)
        CourseEnrollment.enroll(self.user2, self.course.id)
        self.url = reverse('certificate_exception_view', kwargs={'course_id': unicode(self.course.id)})

        certificate_white_list_item = CertificateWhitelistFactory.create(
            user=self.user2,
            course_id=self.course.id,
        )

        self.certificate_exception = dict(
            created="",
            notes="Test Notes for Test Certificate Exception",
            user_email='',
            user_id='',
            user_name=unicode(self.user.username)
        )

        self.certificate_exception_in_db = dict(
            id=certificate_white_list_item.id,
            user_name=certificate_white_list_item.user.username,
            notes=certificate_white_list_item.notes,
            user_email=certificate_white_list_item.user.email,
            user_id=certificate_white_list_item.user.id,
        )

        # Enable certificate generation
        cache.clear()
        CertificateGenerationConfiguration.objects.create(enabled=True)
        self.client.login(username=self.global_staff.username, password='******')
Ejemplo n.º 16
0
    def setUpClass(cls):
        super(TestScoreForModule, cls).setUpClass()
        cls.course = CourseFactory.create()
        with cls.store.bulk_operations(cls.course.id):
            cls.a = ItemFactory.create(parent=cls.course, category="chapter", display_name="a")
            cls.b = ItemFactory.create(parent=cls.a, category="sequential", display_name="b")
            cls.c = ItemFactory.create(parent=cls.a, category="sequential", display_name="c")
            cls.d = ItemFactory.create(parent=cls.b, category="vertical", display_name="d")
            cls.e = ItemFactory.create(parent=cls.b, category="vertical", display_name="e")
            cls.f = ItemFactory.create(parent=cls.b, category="vertical", display_name="f")
            cls.g = ItemFactory.create(parent=cls.c, category="vertical", display_name="g")
            cls.h = ItemFactory.create(parent=cls.d, category="problem", display_name="h")
            cls.i = ItemFactory.create(parent=cls.d, category="problem", display_name="i")
            cls.j = ItemFactory.create(parent=cls.e, category="problem", display_name="j")
            cls.k = ItemFactory.create(parent=cls.e, category="html", display_name="k")
            cls.l = ItemFactory.create(parent=cls.e, category="problem", display_name="l")
            cls.m = ItemFactory.create(parent=cls.f, category="html", display_name="m")
            cls.n = ItemFactory.create(parent=cls.g, category="problem", display_name="n")

        cls.request = get_mock_request(UserFactory())
        CourseEnrollment.enroll(cls.request.user, cls.course.id)

        answer_problem(cls.course, cls.request, cls.h, score=2, max_value=5)
        answer_problem(cls.course, cls.request, cls.i, score=3, max_value=5)
        answer_problem(cls.course, cls.request, cls.j, score=0, max_value=1)
        answer_problem(cls.course, cls.request, cls.l, score=1, max_value=3)
        answer_problem(cls.course, cls.request, cls.n, score=3, max_value=10)

        cls.course_grade = CourseGradeFactory().create(cls.request.user, cls.course)
Ejemplo n.º 17
0
    def test_student_progress_with_valid_and_invalid_id(self, default_store):
        """
         Check that invalid 'student_id' raises Http404 for both old mongo and
         split mongo courses.
        """

        # Create new course with respect to 'default_store'
        self.course = CourseFactory.create(default_store=default_store)

        # Invalid Student Ids (Integer and Non-int)
        invalid_student_ids = [
            991021,
            'azU3N_8$',
        ]
        for invalid_id in invalid_student_ids:

            self.assertRaises(
                Http404, views.progress,
                self.request,
                course_id=unicode(self.course.id),
                student_id=invalid_id
            )

        # Enroll student into course
        CourseEnrollment.enroll(self.user, self.course.id, mode='honor')
        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string(), student_id=self.user.id)
        # Assert that valid 'student_id' returns 200 status
        self.assertEqual(resp.status_code, 200)
Ejemplo n.º 18
0
    def test_access_student_progress_ccx(self):
        """
        Assert that only a coach can see progress of student.
        """
        ccx_locator = self.make_ccx()
        student = UserFactory()

        # Enroll user
        CourseEnrollment.enroll(student, ccx_locator)

        # Test for access of a coach
        request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
        request.user = self.coach
        mako_middleware_process_request(request)
        resp = views.progress(request, course_id=unicode(ccx_locator), student_id=student.id)
        self.assertEqual(resp.status_code, 200)

        # Assert access of a student
        request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
        request.user = student
        mako_middleware_process_request(request)

        with self.assertRaises(Http404) as context:
            views.progress(request, course_id=unicode(ccx_locator), student_id=self.coach.id)

        self.assertIsNotNone(context.exception)
Ejemplo n.º 19
0
    def purchased_callback(self):
        """
        When purchased, this should enroll the user in the course.  We are assuming that
        course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found
        in CourseEnrollmentAllowed will the user be allowed to enroll.  Otherwise requiring payment
        would in fact be quite silly since there's a clear back door.
        """
        try:
            course_loc = CourseDescriptor.id_to_location(self.course_id)
            course_exists = modulestore().has_item(self.course_id, course_loc)
        except ValueError:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id))

        if not course_exists:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id))

        CourseEnrollment.enroll(user=self.user, course_id=self.course_id, mode=self.mode)

        log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost))
        org, course_num, run = self.course_id.split("/")
        dog_stats_api.increment(
            "shoppingcart.PaidCourseRegistration.purchased_callback.enrollment",
            tags=["org:{0}".format(org),
                  "course:{0}".format(course_num),
                  "run:{0}".format(run)]
        )
Ejemplo n.º 20
0
    def test_choose_mode_audit_enroll_on_get(self):
        """
        Confirms that the learner will be enrolled in Audit track if it is the only possible option
        """
        self.mock_enterprise_learner_api()
        self.mock_enterprise_course_enrollment_get_api()
        # Create the course mode
        audit_mode = 'audit'
        CourseModeFactory.create(mode_slug=audit_mode, course_id=self.course.id, min_price=0)

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

        # Choose the audit mode (POST request)
        choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
        response = self.client.get(choose_track_url)

        # Assert learner is enrolled in Audit track and sent to the dashboard
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertEquals(mode, audit_mode)
        self.assertTrue(is_active)

        redirect_url = reverse('dashboard')
        self.assertRedirects(response, redirect_url)
Ejemplo n.º 21
0
    def perform_destroy(self, instance):
        """
        This method is an override and is called by the DELETE method
        """
        save_model = False
        if instance.expired_at is None:
            instance.expired_at = timezone.now()
            log.info('Set expired_at to [%s] for course entitlement [%s]', instance.expired_at, instance.uuid)
            save_model = True

        if instance.enrollment_course_run is not None:
            CourseEnrollment.unenroll(
                user=instance.user,
                course_id=instance.enrollment_course_run.course_id,
                skip_refund=True
            )
            enrollment = instance.enrollment_course_run
            instance.enrollment_course_run = None
            save_model = True
            log.info(
                'Unenrolled user [%s] from course run [%s] as part of revocation of course entitlement [%s]',
                instance.user.username,
                enrollment.course_id,
                instance.uuid
            )
        if save_model:
            instance.save()
Ejemplo n.º 22
0
    def test_check_for_existing_entitlement_and_enroll(self, mock_get_course_uuid):
        course = CourseFactory()
        CourseModeFactory(
            course_id=course.id,
            mode_slug=CourseMode.VERIFIED,
            # This must be in the future to ensure it is returned by downstream code.
            expiration_datetime=now() + timedelta(days=1)
        )
        entitlement = CourseEntitlementFactory.create(
            mode=CourseMode.VERIFIED,
            user=self.user,
        )
        mock_get_course_uuid.return_value = entitlement.course_uuid

        assert not CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)

        CourseEntitlement.check_for_existing_entitlement_and_enroll(
            user=self.user,
            course_run_key=course.id,
        )

        assert CourseEnrollment.is_enrolled(user=self.user, course_key=course.id)

        entitlement.refresh_from_db()
        assert entitlement.enrollment_course_run
Ejemplo n.º 23
0
    def test_unenrollment_email_off(self):
        """
        Do un-enrollment email off test
        """

        course = self.course

        # Run the Un-enroll students command
        url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
        response = self.client.post(
            url,
            {
                'action': 'Unenroll multiple students',
                'multiple_students': '[email protected] [email protected]'
            }
        )

        # Check the page output
        self.assertContains(response, '<td>[email protected]</td>')
        self.assertContains(response, '<td>[email protected]</td>')
        self.assertContains(response, '<td>un-enrolled</td>')

        # Check the enrollment table
        user = User.objects.get(email='*****@*****.**')
        self.assertFalse(CourseEnrollment.is_enrolled(user, course.id))

        user = User.objects.get(email='*****@*****.**')
        self.assertFalse(CourseEnrollment.is_enrolled(user, course.id))

        # Check the outbox
        self.assertEqual(len(mail.outbox), 0)
Ejemplo n.º 24
0
    def test_score_recalculation_on_enrollment_update(self):
        """
        Test that an update in enrollment cause score recalculation.
        Note:
        Score recalculation task must be called with a delay of SCORE_RECALCULATION_DELAY_ON_ENROLLMENT_UPDATE
        """
        course_modes = ['verified', 'audit']

        for mode_slug in course_modes:
            CourseModeFactory.create(
                course_id=self.course.id,
                mode_slug=mode_slug,
                mode_display_name=mode_slug,
            )
        CourseEnrollment.enroll(self.user, self.course.id, mode="audit")

        local_task_args = dict(
            user_id=self.user.id,
            course_key=str(self.course.id)
        )

        with patch(
            'lms.djangoapps.grades.tasks.recalculate_course_and_subsection_grades_for_user.apply_async',
            return_value=None
        ) as mock_task_apply:
            CourseEnrollment.enroll(self.user, self.course.id, mode="verified")
            mock_task_apply.assert_called_once_with(
                countdown=SCORE_RECALCULATION_DELAY_ON_ENROLLMENT_UPDATE,
                kwargs=local_task_args
            )
Ejemplo n.º 25
0
    def setUp(self):
        super(TeamAPITestCase, self).setUp()
        self.topics_count = 4
        self.users = {
            "student_unenrolled": UserFactory.create(password=self.test_password),
            "student_enrolled": UserFactory.create(password=self.test_password),
            "student_enrolled_not_on_team": UserFactory.create(password=self.test_password),
            # This student is enrolled in both test courses and is a member of a team in each course, but is not on the
            # same team as student_enrolled.
            "student_enrolled_both_courses_other_team": UserFactory.create(password=self.test_password),
            "staff": AdminFactory.create(password=self.test_password),
            "course_staff": StaffFactory.create(course_key=self.test_course_1.id, password=self.test_password),
        }
        # 'solar team' is intentionally lower case to test case insensitivity in name ordering
        self.test_team_1 = CourseTeamFactory.create(
            name=u"sólar team", course_id=self.test_course_1.id, topic_id="topic_0"
        )
        self.test_team_2 = CourseTeamFactory.create(name="Wind Team", course_id=self.test_course_1.id)
        self.test_team_3 = CourseTeamFactory.create(name="Nuclear Team", course_id=self.test_course_1.id)
        self.test_team_4 = CourseTeamFactory.create(name="Coal Team", course_id=self.test_course_1.id, is_active=False)
        self.test_team_5 = CourseTeamFactory.create(name="Another Team", course_id=self.test_course_2.id)

        for user, course in [
            ("student_enrolled", self.test_course_1),
            ("student_enrolled_not_on_team", self.test_course_1),
            ("student_enrolled_both_courses_other_team", self.test_course_1),
            ("student_enrolled_both_courses_other_team", self.test_course_2),
        ]:
            CourseEnrollment.enroll(self.users[user], course.id, check_access=True)

        self.test_team_1.add_user(self.users["student_enrolled"])
        self.test_team_3.add_user(self.users["student_enrolled_both_courses_other_team"])
        self.test_team_5.add_user(self.users["student_enrolled_both_courses_other_team"])
Ejemplo n.º 26
0
    def test_refund_cert_callback_before_expiration_email(self):
        """ Test that refund emails are being sent correctly. """
        course = CourseFactory.create()
        course_key = course.id
        many_days = datetime.timedelta(days=60)

        course_mode = CourseMode(course_id=course_key,
                                 mode_slug="verified",
                                 mode_display_name="verified cert",
                                 min_price=self.cost,
                                 expiration_datetime=datetime.datetime.now(pytz.utc) + many_days)
        course_mode.save()

        CourseEnrollment.enroll(self.user, course_key, 'verified')
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, course_key, self.cost, 'verified')
        cart.purchase()

        mail.outbox = []
        with patch('shoppingcart.models.log.error') as mock_error_logger:
            CourseEnrollment.unenroll(self.user, course_key)
            self.assertFalse(mock_error_logger.called)
            self.assertEquals(len(mail.outbox), 1)
            self.assertEquals('[Refund] User-Requested Refund', mail.outbox[0].subject)
            self.assertEquals(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].from_email)
            self.assertIn('has requested a refund on Order', mail.outbox[0].body)
Ejemplo n.º 27
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(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)
Ejemplo n.º 28
0
 def setUp(self):
     super(GradeTestBase, self).setUp()
     self.request = get_request_for_user(UserFactory())
     self.client.login(username=self.request.user.username, password="******")
     self.subsection_grade_factory = SubsectionGradeFactory(self.request.user)
     self.course_structure = get_course_blocks(self.request.user, self.course.location)
     CourseEnrollment.enroll(self.request.user, self.course.id)
Ejemplo n.º 29
0
    def test_refund_cert_callback_before_expiration(self):
        # If the expiration date has not yet passed on a verified mode, the user can be refunded
        many_days = datetime.timedelta(days=60)

        course = CourseFactory.create()
        self.course_key = course.id
        course_mode = CourseMode(course_id=self.course_key,
                                 mode_slug="verified",
                                 mode_display_name="verified cert",
                                 min_price=self.cost,
                                 expiration_datetime=(datetime.datetime.now(pytz.utc) + many_days))
        course_mode.save()

        # need to prevent analytics errors from appearing in stderr
        with patch('sys.stderr', sys.stdout.write):
            CourseEnrollment.enroll(self.user, self.course_key, 'verified')
            cart = Order.get_cart_for_user(user=self.user)
            CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified')
            cart.purchase()
            CourseEnrollment.unenroll(self.user, self.course_key)

        target_certs = CertificateItem.objects.filter(course_id=self.course_key, user_id=self.user, status='refunded', mode='verified')
        self.assertTrue(target_certs[0])
        self.assertTrue(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, 'refunded')
        self._assert_refund_tracked()
Ejemplo n.º 30
0
 def test_no_duplicate_emails_enrolled_staff(self):
     """
     Test that no duplicate emails are sent to a course instructor that is
     also enrolled in the course
     """
     CourseEnrollment.enroll(self.instructor, self.course.id)
     self.test_send_to_all()
Ejemplo n.º 31
0
 def test_no_upgrade_message_if_not_enrolled(self):
     self.assertEqual(len(CourseEnrollment.enrollments_for_user(self.user)), 0)
     self.assert_upgrade_message_not_displayed()
Ejemplo n.º 32
0
def user_foo_is_enrolled_in_the_course(step, name):
    world.create_user(name, 'test')
    user = User.objects.get(username=name)

    course_id = world.scenario_dict['COURSE'].id
    CourseEnrollment.enroll(user, course_id)
Ejemplo n.º 33
0
def user_profile(request, course_key, user_id):
    """
    Renders a response to display the user profile page (shown after clicking
    on a post author's username).
    """

    nr_transaction = newrelic.agent.current_transaction()

    #TODO: Allow sorting?
    course = get_course_with_access(request.user,
                                    'load',
                                    course_key,
                                    check_if_enrolled=True)

    try:
        # If user is not enrolled in the course, do not proceed.
        django_user = User.objects.get(id=user_id)
        if not CourseEnrollment.is_enrolled(django_user, course.id):
            raise Http404

        query_params = {
            'page': request.GET.get('page', 1),
            'per_page':
            THREADS_PER_PAGE,  # more than threads_per_page to show more activities
        }

        try:
            group_id = get_group_id_for_comments_service(request, course_key)
        except ValueError:
            return HttpResponseBadRequest("Invalid group_id")
        if group_id is not None:
            query_params['group_id'] = group_id
            profiled_user = cc.User(id=user_id,
                                    course_id=course_key,
                                    group_id=group_id)
        else:
            profiled_user = cc.User(id=user_id, course_id=course_key)

        threads, page, num_pages = profiled_user.active_threads(query_params)
        query_params['page'] = page
        query_params['num_pages'] = num_pages
        user_info = cc.User.from_django_user(request.user).to_dict()

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_metadata_for_threads"):
            annotated_content_info = utils.get_metadata_for_threads(
                course_key, threads, request.user, user_info)

        is_staff = has_permission(request.user, 'openclose_thread', course.id)
        threads = [
            utils.prepare_content(thread, course_key, is_staff)
            for thread in threads
        ]
        if request.is_ajax():
            return utils.JsonResponse({
                'discussion_data':
                threads,
                'page':
                query_params['page'],
                'num_pages':
                query_params['num_pages'],
                'annotated_content_info':
                annotated_content_info,
            })
        else:
            user_roles = django_user.roles.filter(
                course_id=course.id).order_by("name").values_list(
                    "name", flat=True).distinct()

            context = {
                'course':
                course,
                'user':
                request.user,
                'django_user':
                django_user,
                'django_user_roles':
                user_roles,
                'profiled_user':
                profiled_user.to_dict(),
                'threads':
                threads,
                'user_info':
                user_info,
                'annotated_content_info':
                annotated_content_info,
                'page':
                query_params['page'],
                'num_pages':
                query_params['num_pages'],
                'learner_profile_page_url':
                reverse('learner_profile',
                        kwargs={'username': django_user.username}),
                'disable_courseware_js':
                True,
                'uses_pattern_library':
                True,
            }

            return render_to_response(
                'discussion/discussion_profile_page.html', context)
    except User.DoesNotExist:
        raise Http404
Ejemplo n.º 34
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(
            u"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_metric('course_id', text_type(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(u"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_ip(request), 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=[six.text_type(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': text_type(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"))
Ejemplo n.º 35
0
 def setUpTestData(cls):
     """Set up and enroll our fake user in the course."""
     cls.user = UserFactory(password=TEST_PASSWORD)
     CourseEnrollment.enroll(cls.user, cls.course.id)
     cls.site = Site.objects.get_current()
Ejemplo n.º 36
0
 def setUpTestData(cls):
     """Set up and enroll our fake user in the course."""
     cls.user = UserFactory(password=TEST_PASSWORD)
     for course in cls.courses:
         CourseEnrollment.enroll(cls.user, course.id)
Ejemplo n.º 37
0
 def test_no_upgrade_message_if_verified_track(self):
     CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
     self.assert_upgrade_message_not_displayed()
Ejemplo n.º 38
0
 def setUpTestData(cls):
     """Set up and enroll our fake user in the course."""
     super(CourseHomePageTestCase, cls).setUpTestData()
     cls.staff_user = StaffFactory(course_key=cls.course.id, password=TEST_PASSWORD)
     cls.user = UserFactory(password=TEST_PASSWORD)
     CourseEnrollment.enroll(cls.user, cls.course.id)
Ejemplo n.º 39
0
 def test_no_upgrade_message_if_flag_disabled(self):
     self.flag.everyone = False
     self.flag.save()
     CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT)
     self.assert_upgrade_message_not_displayed()
Ejemplo n.º 40
0
    def test_course_messaging(self):
        """
        Ensure that the following four use cases work as expected

        1) Anonymous users are shown a course message linking them to the login page
        2) Unenrolled users are shown a course message allowing them to enroll
        3) Enrolled users who show up on the course page after the course has begun
        are not shown a course message.
        4) Enrolled users who show up on the course page after the course has begun will
        see the course expiration banner if course duration limits are on for the course.
        5) Enrolled users who show up on the course page before the course begins
        are shown a message explaining when the course starts as well as a call to
        action button that allows them to add a calendar event.
        """
        # Verify that anonymous users are shown a login link in the course message
        url = course_home_url(self.course)
        response = self.client.get(url)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS)

        # Verify that unenrolled users are shown an enroll call to action message
        user = self.create_user_for_course(self.course, CourseUserType.UNENROLLED)
        url = course_home_url(self.course)
        response = self.client.get(url)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)

        # Verify that enrolled users are not shown any state warning message when enrolled and course has begun.
        CourseEnrollment.enroll(user, self.course.id)
        url = course_home_url(self.course)
        response = self.client.get(url)
        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS)
        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)

        # Verify that enrolled users are shown the course expiration banner if content gating is enabled

        # We use .save() explicitly here (rather than .objects.create) in order to force the
        # cache to refresh.
        config = CourseDurationLimitConfig(
            course=CourseOverview.get_from_id(self.course.id),
            enabled=True,
            enabled_as_of=datetime(2018, 1, 1)
        )
        config.save()

        url = course_home_url(self.course)
        response = self.client.get(url)
        bannerText = get_expiration_banner_text(user, self.course)
        self.assertContains(response, bannerText, html=True)

        # Verify that enrolled users are not shown the course expiration banner if content gating is disabled
        config.enabled = False
        config.save()
        url = course_home_url(self.course)
        response = self.client.get(url)
        bannerText = get_expiration_banner_text(user, self.course)
        self.assertNotContains(response, bannerText, html=True)

        # Verify that enrolled users are shown 'days until start' message before start date
        future_course = self.create_future_course()
        CourseEnrollment.enroll(user, future_course.id)
        url = course_home_url(future_course)
        response = self.client.get(url)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
        self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
Ejemplo n.º 41
0
 def test_not_enrolled(self):
     CourseEnrollment.unenroll(self.user, self.course_key)
     self.verify_response(403)
Ejemplo n.º 42
0
 def test_display_upgrade_message_if_audit_and_deadline_not_passed(self):
     CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT)
     self.assert_upgrade_message_displayed()
Ejemplo n.º 43
0
 def setUp(self):
     super(ComputeGradesForCourseTest, self).setUp()
     self.users = [UserFactory.create() for _ in xrange(12)]
     self.set_up_course()
     for user in self.users:
         CourseEnrollment.enroll(user, self.course.id)
Ejemplo n.º 44
0
    def test_transfer_students(self):
        """ Verify the transfer student command works as intended. """
        student = UserFactory.create()
        student.set_password(self.PASSWORD)  # pylint: disable=E1101
        student.save()  # pylint: disable=E1101
        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 = unicode(course.id)
        new_key_one = unicode(new_course_one.id)
        new_key_two = unicode(new_course_two.id)

        # Run the actual management command
        transfer_students.Command().handle(source_course=original_key,
                                           dest_course_list=new_key_one + "," +
                                           new_key_two)
        self.assertTrue(self.signal_fired)

        # Confirm the analytics event was emitted.
        self.mock_tracker.emit.assert_has_calls(  # pylint: disable=E1103
            [
                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.assertEquals(
            (mode, False),
            CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEquals(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_one.id))
        self.assertEquals(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_two.id))

        # Confirm the student has not be refunded.
        target_certs = CertificateItem.objects.filter(course_id=course.id,
                                                      user_id=student,
                                                      status='purchased',
                                                      mode=mode)
        self.assertTrue(target_certs[0])
        self.assertFalse(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, 'purchased')
Ejemplo n.º 45
0
    def test_enrollment_limit_by_domain(self):
        """
            Tests that the enrollmentDomain setting is properly limiting enrollment to those who have
            the proper external auth
        """

        # create 2 course, one with limited enrollment one without
        shib_course = CourseFactory.create(
            org='Stanford',
            number='123',
            display_name='Shib Only',
            enrollment_domain='shib:https://idp.stanford.edu/',
            user_id=self.test_user_id,
        )

        open_enroll_course = CourseFactory.create(
            org='MITx',
            number='999',
            display_name='Robot Super Course',
            enrollment_domain='',
            user_id=self.test_user_id,
        )

        # create 3 kinds of students, external_auth matching shib_course, external_auth not matching, no external auth
        shib_student = UserFactory.create()
        shib_student.save()
        extauth = ExternalAuthMap(external_id='*****@*****.**',
                                  external_email='',
                                  external_domain='shib:https://idp.stanford.edu/',
                                  external_credentials="",
                                  user=shib_student)
        extauth.save()

        other_ext_student = UserFactory.create()
        other_ext_student.username = "******"
        other_ext_student.email = "*****@*****.**"
        other_ext_student.save()
        extauth = ExternalAuthMap(external_id='*****@*****.**',
                                  external_email='',
                                  external_domain='shib:https://other.edu/',
                                  external_credentials="",
                                  user=other_ext_student)
        extauth.save()

        int_student = UserFactory.create()
        int_student.username = "******"
        int_student.email = "*****@*****.**"
        int_student.save()

        # Tests the two case for courses, limited and not
        for course in [shib_course, open_enroll_course]:
            for student in [shib_student, other_ext_student, int_student]:
                request = self.request_factory.post('/change_enrollment')

                request.POST.update({'enrollment_action': 'enroll',
                                     'course_id': course.id.to_deprecated_string()})
                request.user = student
                response = change_enrollment(request)
                # If course is not limited or student has correct shib extauth then enrollment should be allowed
                if course is open_enroll_course or student is shib_student:
                    self.assertEqual(response.status_code, 200)
                    self.assertTrue(CourseEnrollment.is_enrolled(student, course.id))
                else:
                    self.assertEqual(response.status_code, 400)
                    self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))
Ejemplo n.º 46
0
 def setUp(self):
     super(RecalculateGradesForUserTest, self).setUp()
     self.user = UserFactory.create()
     self.set_up_course()
     CourseEnrollment.enroll(self.user, self.course.id)
Ejemplo n.º 47
0
def _show_receipt_html(request, order):
    """Render the receipt page as HTML.

    Arguments:
        request (HttpRequest): The request for the receipt.
        order (Order): The order model to display.

    Returns:
        HttpResponse

    """
    order_items = OrderItem.objects.filter(order=order).select_subclasses()
    shoppingcart_items = []
    course_names_list = []
    for order_item in order_items:
        course_key = getattr(order_item, 'course_id')
        if course_key:
            course = get_course_by_id(course_key, depth=0)
            shoppingcart_items.append((order_item, course))
            course_names_list.append(course.display_name)

    appended_course_names = ", ".join(course_names_list)
    any_refunds = any(i.status == "refunded" for i in order_items)
    receipt_template = 'shoppingcart/receipt.html'
    __, instructions = order.generate_receipt_instructions()
    order_type = getattr(order, 'order_type')

    # Only orders where order_items.count() == 1 might be attempting to upgrade
    attempting_upgrade = request.session.get('attempting_upgrade', False)
    if attempting_upgrade:
        course_enrollment = CourseEnrollment.get_or_create_enrollment(
            request.user, order_items[0].course_id)
        course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED)
        request.session['attempting_upgrade'] = False

    recipient_list = []
    total_registration_codes = None
    reg_code_info_list = []
    recipient_list.append(getattr(order.user, 'email'))
    if order_type == OrderTypes.BUSINESS:
        if order.company_contact_email:
            recipient_list.append(order.company_contact_email)
        if order.recipient_email:
            recipient_list.append(order.recipient_email)

        for __, course in shoppingcart_items:
            course_registration_codes = CourseRegistrationCode.objects.filter(
                order=order, course_id=course.id)
            total_registration_codes = course_registration_codes.count()
            for course_registration_code in course_registration_codes:
                reg_code_info_list.append({
                    'course_name':
                    course.display_name,
                    'redemption_url':
                    reverse('register_code_redemption',
                            args=[course_registration_code.code]),
                    'code':
                    course_registration_code.code,
                    'is_redeemed':
                    RegistrationCodeRedemption.objects.filter(
                        registration_code=course_registration_code).exists(),
                })

    appended_recipient_emails = ", ".join(recipient_list)

    context = {
        'order': order,
        'shoppingcart_items': shoppingcart_items,
        'any_refunds': any_refunds,
        'instructions': instructions,
        'site_name': microsite.get_value('SITE_NAME', settings.SITE_NAME),
        'order_type': order_type,
        'appended_course_names': appended_course_names,
        'appended_recipient_emails': appended_recipient_emails,
        'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1],
        'currency': settings.PAID_COURSE_REGISTRATION_CURRENCY[0],
        'total_registration_codes': total_registration_codes,
        'reg_code_info_list': reg_code_info_list,
        'order_purchase_date': order.purchase_time.strftime("%B %d, %Y"),
    }

    # We want to have the ability to override the default receipt page when
    # there is only one item in the order.
    if order_items.count() == 1:
        receipt_template = order_items[0].single_item_receipt_template
        context.update(order_items[0].single_item_receipt_context)

    return render_to_response(receipt_template, context)
Ejemplo n.º 48
0
def results_callback(request):
    """
    Software Secure will call this callback to tell us whether a user is
    verified to be who they said they are.
    """
    body = request.body

    try:
        body_dict = json.loads(body)
    except ValueError:
        log.exception(
            "Invalid JSON received from Software Secure:\n\n{}\n".format(body))
        return HttpResponseBadRequest(
            "Invalid JSON. Received:\n\n{}".format(body))

    if not isinstance(body_dict, dict):
        log.error(
            "Reply from Software Secure is not a dict:\n\n{}\n".format(body))
        return HttpResponseBadRequest(
            "JSON should be dict. Received:\n\n{}".format(body))

    headers = {
        "Authorization": request.META.get("HTTP_AUTHORIZATION", ""),
        "Date": request.META.get("HTTP_DATE", "")
    }

    sig_valid = ssencrypt.has_valid_signature(
        "POST", headers, body_dict,
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"],
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_SECRET_KEY"])

    _response, access_key_and_sig = headers["Authorization"].split(" ")
    access_key = access_key_and_sig.split(":")[0]

    # This is what we should be doing...
    #if not sig_valid:
    #    return HttpResponseBadRequest("Signature is invalid")

    # This is what we're doing until we can figure out why we disagree on sigs
    if access_key != settings.VERIFY_STUDENT["SOFTWARE_SECURE"][
            "API_ACCESS_KEY"]:
        return HttpResponseBadRequest("Access key invalid")

    receipt_id = body_dict.get("EdX-ID")
    result = body_dict.get("Result")
    reason = body_dict.get("Reason", "")
    error_code = body_dict.get("MessageType", "")

    try:
        attempt = SoftwareSecurePhotoVerification.objects.get(
            receipt_id=receipt_id)
    except SoftwareSecurePhotoVerification.DoesNotExist:
        log.error(
            "Software Secure posted back for receipt_id {}, but not found".
            format(receipt_id))
        return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))

    if result == "PASS":
        log.debug("Approving verification for {}".format(receipt_id))
        attempt.approve()
    elif result == "FAIL":
        log.debug("Denying verification for {}".format(receipt_id))
        attempt.deny(json.dumps(reason), error_code=error_code)
    elif result == "SYSTEM FAIL":
        log.debug("System failure for {} -- resetting to must_retry".format(
            receipt_id))
        attempt.system_error(json.dumps(reason), error_code=error_code)
        log.error("Software Secure callback attempt for %s failed: %s",
                  receipt_id, reason)
    else:
        log.error("Software Secure returned unknown result {}".format(result))
        return HttpResponseBadRequest(
            "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".
            format(result))

    # If this is a reverification, log an event
    if attempt.window:
        course_id = attempt.window.course_id
        course_enrollment = CourseEnrollment.get_or_create_enrollment(
            attempt.user, course_id)
        course_enrollment.emit_event(
            EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE)

    return HttpResponse("OK!")
Ejemplo n.º 49
0
    def test_enrollment_multiple_classes(self):
        user = User(username="******", email="*****@*****.**")
        course_id1 = SlashSeparatedCourseKey("edX", "Test101", "2013")
        course_id2 = SlashSeparatedCourseKey("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)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id1))
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id2))

        CourseEnrollment.unenroll(user, course_id1)
        self.assert_unenrollment_event_was_emitted(user, course_id1)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id1))
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id2))

        CourseEnrollment.unenroll(user, course_id2)
        self.assert_unenrollment_event_was_emitted(user, course_id2)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id1))
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id2))
Ejemplo n.º 50
0
    def setUp(self):
        super(ReportTypeTests, self).setUp()

        # Need to make a *lot* of users for this one
        self.first_verified_user = UserFactory.create(profile__name="John Doe")
        self.second_verified_user = UserFactory.create(
            profile__name="Jane Deer")
        self.first_audit_user = UserFactory.create(profile__name="Joe Miller")
        self.second_audit_user = UserFactory.create(
            profile__name="Simon Blackquill")
        self.third_audit_user = UserFactory.create(profile__name="Super Mario")
        self.honor_user = UserFactory.create(profile__name="Princess Peach")
        self.first_refund_user = UserFactory.create(
            profile__name="King Bowsér")
        self.second_refund_user = UserFactory.create(
            profile__name="Súsan Smith")

        # Two are verified, three are audit, one honor

        self.cost = 40
        self.course = CourseFactory.create(org='MITx',
                                           number='999',
                                           display_name=u'Robot Super Course')
        self.course_key = self.course.id
        settings.COURSE_LISTINGS['default'] = [
            self.course_key.to_deprecated_string()
        ]
        course_mode = CourseMode(course_id=self.course_key,
                                 mode_slug="honor",
                                 mode_display_name="honor cert",
                                 min_price=self.cost)
        course_mode.save()

        course_mode2 = CourseMode(course_id=self.course_key,
                                  mode_slug="verified",
                                  mode_display_name="verified cert",
                                  min_price=self.cost)
        course_mode2.save()

        # User 1 & 2 will be verified
        self.cart1 = Order.get_cart_for_user(self.first_verified_user)
        CertificateItem.add_to_order(self.cart1, self.course_key, self.cost,
                                     'verified')
        self.cart1.purchase()

        self.cart2 = Order.get_cart_for_user(self.second_verified_user)
        CertificateItem.add_to_order(self.cart2, self.course_key, self.cost,
                                     'verified')
        self.cart2.purchase()

        # Users 3, 4, and 5 are audit
        CourseEnrollment.enroll(self.first_audit_user, self.course_key,
                                "audit")
        CourseEnrollment.enroll(self.second_audit_user, self.course_key,
                                "audit")
        CourseEnrollment.enroll(self.third_audit_user, self.course_key,
                                "audit")

        # User 6 is honor
        CourseEnrollment.enroll(self.honor_user, self.course_key, "honor")

        self.now = datetime.datetime.now(pytz.UTC)

        # Users 7 & 8 are refunds
        self.cart = Order.get_cart_for_user(self.first_refund_user)
        CertificateItem.add_to_order(self.cart, self.course_key, self.cost,
                                     'verified')
        self.cart.purchase()
        CourseEnrollment.unenroll(self.first_refund_user, self.course_key)

        self.cart = Order.get_cart_for_user(self.second_refund_user)
        CertificateItem.add_to_order(self.cart, self.course_key, self.cost,
                                     'verified')
        self.cart.purchase(self.second_refund_user.username, self.course_key)
        CourseEnrollment.unenroll(self.second_refund_user, self.course_key)

        self.test_time = datetime.datetime.now(pytz.UTC)

        first_refund = CertificateItem.objects.get(id=3)
        first_refund.fulfilled_time = self.test_time
        first_refund.refund_requested_time = self.test_time
        first_refund.save()

        second_refund = CertificateItem.objects.get(id=4)
        second_refund.fulfilled_time = self.test_time
        second_refund.refund_requested_time = self.test_time
        second_refund.save()

        self.CORRECT_REFUND_REPORT_CSV = dedent("""
            Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any)
            3,King Bowsér,{time_str},{time_str},40.00,0.00
            4,Súsan Smith,{time_str},{time_str},40.00,0.00
            """.format(time_str=str(self.test_time)))

        self.CORRECT_CERT_STATUS_CSV = dedent("""
            University,Course,Course Announce Date,Course Start Date,Course Registration Close Date,Course Registration Period,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Verified Students Contributing More than the Minimum,Number of Refunds,Dollars Refunded
            MITx,999 Robot Super Course,,,,,6,3,1,2,80.00,0.00,0,2,80.00
            """.format(time_str=str(self.test_time)))

        self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent("""
            University,Course,Number of Transactions,Total Payments Collected,Service Fees (if any),Number of Successful Refunds,Total Amount of Refunds
            MITx,999 Robot Super Course,6,80.00,0.00,2,80.00
            """.format(time_str=str(self.test_time)))
Ejemplo n.º 51
0
    def test_enrollment(self):
        user = User.objects.create_user("joe", "*****@*****.**", "password")
        course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")
        course_id_partial = SlashSeparatedCourseKey("edX", "Test101", None)

        # 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.assertEquals(enrollment.mode, "verified")
        CourseEnrollment.unenroll(user, course_id)
        enrollment = CourseEnrollment.enroll(user, course_id, "audit")
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertEquals(enrollment.mode, "audit")
Ejemplo n.º 52
0
def register_code_redemption(request, registration_code):
    """
    This view allows the student to redeem the registration code
    and enroll in the course.
    """

    # Add some rate limiting here by re-using the RateLimitMixin as a helper class
    site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME)
    limiter = BadRequestRateLimiter()
    if limiter.is_rate_limit_exceeded(request):
        AUDIT_LOG.warning(
            "Rate limit exceeded in registration code redemption.")
        return HttpResponseForbidden()

    template_to_render = 'shoppingcart/registration_code_redemption.html'
    if request.method == "GET":
        reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(
            registration_code, request, limiter)
        course = get_course_by_id(getattr(course_registration, 'course_id'),
                                  depth=0)

        # Restrict the user from enrolling based on country access rules
        embargo_redirect = embargo_api.redirect_if_blocked(
            course.id,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path)
        if embargo_redirect is not None:
            return redirect(embargo_redirect)

        context = {
            'reg_code_already_redeemed':
            reg_code_already_redeemed,
            'reg_code_is_valid':
            reg_code_is_valid,
            'reg_code':
            registration_code,
            'site_name':
            site_name,
            'course':
            course,
            'registered_for_course':
            not _is_enrollment_code_an_update(course, request.user,
                                              course_registration)
        }
        return render_to_response(template_to_render, context)
    elif request.method == "POST":
        reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(
            registration_code, request, limiter)
        course = get_course_by_id(getattr(course_registration, 'course_id'),
                                  depth=0)

        # Restrict the user from enrolling based on country access rules
        embargo_redirect = embargo_api.redirect_if_blocked(
            course.id,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path)
        if embargo_redirect is not None:
            return redirect(embargo_redirect)

        context = {
            'reg_code': registration_code,
            'site_name': site_name,
            'course': course,
            'reg_code_is_valid': reg_code_is_valid,
            'reg_code_already_redeemed': reg_code_already_redeemed,
        }
        if reg_code_is_valid and not reg_code_already_redeemed:
            # remove the course from the cart if it was added there.
            cart = Order.get_cart_for_user(request.user)
            try:
                cart_items = cart.find_item_by_course_id(
                    course_registration.course_id)

            except ItemNotFoundInCartException:
                pass
            else:
                for cart_item in cart_items:
                    if isinstance(cart_item,
                                  PaidCourseRegistration) or isinstance(
                                      cart_item, CourseRegCodeItem):
                        cart_item.delete()

            #now redeem the reg code.
            redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption(
                course_registration, request.user)
            try:
                kwargs = {}
                if course_registration.mode_slug is not None:
                    if CourseMode.mode_for_course(
                            course.id, course_registration.mode_slug):
                        kwargs['mode'] = course_registration.mode_slug
                    else:
                        raise RedemptionCodeError()
                redemption.course_enrollment = CourseEnrollment.enroll(
                    request.user, course.id, **kwargs)
                redemption.save()
                context['redemption_success'] = True
            except RedemptionCodeError:
                context['redeem_code_error'] = True
                context['redemption_success'] = False
            except EnrollmentClosedError:
                context['enrollment_closed'] = True
                context['redemption_success'] = False
            except CourseFullError:
                context['course_full'] = True
                context['redemption_success'] = False
            except AlreadyEnrolledError:
                context['registered_for_course'] = True
                context['redemption_success'] = False
        else:
            context['redemption_success'] = False
        return render_to_response(template_to_render, context)
Ejemplo n.º 53
0
def enroll_email(course_id,
                 student_email,
                 auto_enroll=False,
                 email_students=False,
                 email_params=None,
                 language=None):
    """
    Enroll a student by email.

    `student_email` is student's emails e.g. "*****@*****.**"
    `auto_enroll` determines what is put in CourseEnrollmentAllowed.auto_enroll
        if auto_enroll is set, then when the email registers, they will be
        enrolled in the course automatically.
    `email_students` determines if student should be notified of action by email.
    `email_params` parameters used while parsing email templates (a `dict`).
    `language` is the language used to render the email.

    returns two EmailEnrollmentState's
        representing state before and after the action.
    """
    previous_state = EmailEnrollmentState(course_id, student_email)
    enrollment_obj = None
    if previous_state.user:
        # if the student is currently unenrolled, don't enroll them in their
        # previous mode

        # for now, White Labels use 'shoppingcart' which is based on the
        # "honor" course_mode. Given the change to use "audit" as the default
        # course_mode in Open edX, we need to be backwards compatible with
        # how White Labels approach enrollment modes.
        if CourseMode.is_white_label(course_id):
            course_mode = CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
        else:
            course_mode = None

        if previous_state.enrollment:
            course_mode = previous_state.mode

        enrollment_obj = CourseEnrollment.enroll_by_email(
            student_email, course_id, course_mode)
        if email_students:
            email_params['message'] = 'enrolled_enroll'
            email_params['email_address'] = student_email
            email_params['full_name'] = previous_state.full_name
            email_params['user_is_active'] = previous_state.user_active
            send_mail_to_student(student_email,
                                 email_params,
                                 language=language)
    else:
        cea, _ = CourseEnrollmentAllowed.objects.get_or_create(
            course_id=course_id, email=student_email)
        cea.auto_enroll = auto_enroll
        cea.save()
        if email_students:
            email_params['message'] = 'allowed_enroll'
            email_params['email_address'] = student_email
            send_mail_to_student(student_email,
                                 email_params,
                                 language=language)

    after_state = EmailEnrollmentState(course_id, student_email)

    return previous_state, after_state, enrollment_obj
Ejemplo n.º 54
0
    def test_enrollment_by_email(self):
        user = User.objects.create(username="******", email="*****@*****.**")
        course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")

        CourseEnrollment.enroll_by_email("*****@*****.**", course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assert_enrollment_event_was_emitted(user, course_id)

        # This won't throw an exception, even though the user is not found
        self.assertIsNone(
            CourseEnrollment.enroll_by_email("*****@*****.**",
                                             course_id))
        self.assert_no_events_were_emitted()

        self.assertRaises(User.DoesNotExist,
                          CourseEnrollment.enroll_by_email,
                          "*****@*****.**",
                          course_id,
                          ignore_errors=False)
        self.assert_no_events_were_emitted()

        # Now unenroll them by email
        CourseEnrollment.unenroll_by_email("*****@*****.**", course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assert_unenrollment_event_was_emitted(user, course_id)

        # Harmless second unenroll
        CourseEnrollment.unenroll_by_email("*****@*****.**", course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assert_no_events_were_emitted()

        # Unenroll on non-existent user shouldn't throw an error
        CourseEnrollment.unenroll_by_email("*****@*****.**", course_id)
        self.assert_no_events_were_emitted()
 def tearDown(self):
     CourseEnrollment.unenroll(self.user, self.course.id)
     super(CourseExpirationTestCase, self).tearDown()
Ejemplo n.º 56
0
def check_verify_status_by_course(user, course_enrollments):
    """
    Determine the per-course verification statuses for a given user.

    The possible statuses are:
        * VERIFY_STATUS_NEED_TO_VERIFY: The student has not yet submitted photos for verification.
        * VERIFY_STATUS_SUBMITTED: The student has submitted photos for verification,
          but has have not yet been approved.
        * VERIFY_STATUS_RESUBMITTED: The student has re-submitted photos for re-verification while
          they still have an active but expiring ID verification
        * VERIFY_STATUS_APPROVED: The student has been successfully verified.
        * VERIFY_STATUS_MISSED_DEADLINE: The student did not submit photos within the course's deadline.
        * VERIFY_STATUS_NEED_TO_REVERIFY: The student has an active verification, but it is
            set to expire before the verification deadline for the course.

    It is is also possible that a course does NOT have a verification status if:
        * The user is not enrolled in a verified mode, meaning that the user didn't pay.
        * The course does not offer a verified mode.
        * The user submitted photos but an error occurred while verifying them.
        * The user submitted photos but the verification was denied.

    In the last two cases, we rely on messages in the sidebar rather than displaying
    messages for each course.

    Arguments:
        user (User): The currently logged-in user.
        course_enrollments (list[CourseEnrollment]): The courses the user is enrolled in.

    Returns:
        dict: Mapping of course keys verification status dictionaries.
            If no verification status is applicable to a course, it will not
            be included in the dictionary.
            The dictionaries have these keys:
                * status (str): One of the enumerated status codes.
                * days_until_deadline (int): Number of days until the verification deadline.
                * verification_good_until (str): Date string for the verification expiration date.

    """
    status_by_course = {}

    # Retrieve all verifications for the user, sorted in descending
    # order by submission datetime
    verifications = IDVerificationService.verifications_for_user(user)

    # Check whether the user has an active or pending verification attempt
    has_active_or_pending = IDVerificationService.user_has_valid_or_pending(
        user)

    # Retrieve expiration_datetime of most recent approved verification
    expiration_datetime = IDVerificationService.get_expiration_datetime(
        user, ['approved'])
    verification_expiring_soon = is_verification_expiring_soon(
        expiration_datetime)

    # Retrieve verification deadlines for the enrolled courses
    course_deadlines = VerificationDeadline.deadlines_for_enrollments(
        CourseEnrollment.enrollments_for_user(user))
    recent_verification_datetime = None

    for enrollment in course_enrollments:

        # If the user hasn't enrolled as verified, then the course
        # won't display state related to its verification status.
        if enrollment.mode in CourseMode.VERIFIED_MODES:

            # Retrieve the verification deadline associated with the course.
            # This could be None if the course doesn't have a deadline.
            deadline = course_deadlines.get(enrollment.course_id)

            relevant_verification = verification_for_datetime(
                deadline, verifications)

            # Picking the max verification datetime on each iteration only with approved status
            if relevant_verification is not None and relevant_verification.status == "approved":
                recent_verification_datetime = max(
                    recent_verification_datetime
                    if recent_verification_datetime is not None else
                    relevant_verification.expiration_datetime,
                    relevant_verification.expiration_datetime)

            # By default, don't show any status related to verification
            status = None
            should_display = True

            # Check whether the user was approved or is awaiting approval
            if relevant_verification is not None:
                should_display = relevant_verification.should_display_status_to_user(
                )

                if relevant_verification.status == "approved":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    else:
                        status = VERIFY_STATUS_APPROVED
                elif relevant_verification.status == "submitted":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_RESUBMITTED
                    else:
                        status = VERIFY_STATUS_SUBMITTED

            # If the user didn't submit at all, then tell them they need to verify
            # If the deadline has already passed, then tell them they missed it.
            # If they submitted but something went wrong (error or denied),
            # then don't show any messaging next to the course, since we already
            # show messages related to this on the left sidebar.
            submitted = (relevant_verification is not None
                         and relevant_verification.status
                         not in ["created", "ready"])
            if status is None and not submitted:
                if deadline is None or deadline > datetime.now(UTC):
                    if IDVerificationService.user_is_verified(
                            user) and verification_expiring_soon:
                        # The user has an active verification, but the verification
                        # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
                        # Tell the student to reverify.
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    elif not IDVerificationService.user_is_verified(user):
                        status = VERIFY_STATUS_NEED_TO_VERIFY
                else:
                    # If a user currently has an active or pending verification,
                    # then they may have submitted an additional attempt after
                    # the verification deadline passed.  This can occur,
                    # for example, when the support team asks a student
                    # to reverify after the deadline so they can receive
                    # a verified certificate.
                    # In this case, we still want to show them as "verified"
                    # on the dashboard.
                    if has_active_or_pending:
                        status = VERIFY_STATUS_APPROVED

                    # Otherwise, the student missed the deadline, so show
                    # them as "honor" (the kind of certificate they will receive).
                    else:
                        status = VERIFY_STATUS_MISSED_DEADLINE

            # Set the status for the course only if we're displaying some kind of message
            # Otherwise, leave the course out of the dictionary.
            if status is not None:
                days_until_deadline = None

                now = datetime.now(UTC)
                if deadline is not None and deadline > now:
                    days_until_deadline = (deadline - now).days

                status_by_course[enrollment.course_id] = {
                    'status': status,
                    'days_until_deadline': days_until_deadline,
                    'should_display': should_display,
                }

    if recent_verification_datetime:
        for key, value in iteritems(status_by_course):  # pylint: disable=unused-variable
            status_by_course[key][
                'verification_good_until'] = recent_verification_datetime.strftime(
                    "%m/%d/%Y")

    return status_by_course
 def test_with_invalid_course_id(self):
     CourseEnrollment.enroll(self.user, self.course.id, mode="honor")
     resp = self._change_enrollment('unenroll', course_id="edx/")
     self.assertEqual(resp.status_code, 400)
 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))
     self.assertEqual(result, None)
Ejemplo n.º 59
0
    def render_to_fragment(self, request, course_id=None, **kwargs):
        """
        Renders the course's home page as a fragment.
        """
        course_key = CourseKey.from_string(course_id)
        course = get_course_with_access(request.user, 'load', course_key)

        # Render the course dates as a fragment
        dates_fragment = CourseDatesFragmentView().render_to_fragment(
            request, course_id=course_id, **kwargs)

        # Render the full content to enrolled users, as well as to course and global staff.
        # Unenrolled users who are not course or global staff are given only a subset.
        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        user_access = {
            'is_anonymous': request.user.is_anonymous,
            'is_enrolled': enrollment and enrollment.is_active,
            'is_staff': has_access(request.user, 'staff', course_key),
        }

        allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(
            course_key)
        allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC
        allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE

        # Set all the fragments
        outline_fragment = None
        update_message_fragment = None
        course_sock_fragment = None
        offer_banner_fragment = None
        next_up_banner_fragment = None
        course_expiration_fragment = None
        has_visited_course = None
        resume_course_url = None
        handouts_html = None

        course_overview = CourseOverview.get_from_id(course.id)
        if user_access['is_enrolled'] or user_access['is_staff']:
            outline_fragment = CourseOutlineFragmentView().render_to_fragment(
                request, course_id=course_id, **kwargs)
            if LATEST_UPDATE_FLAG.is_enabled(course_key):
                update_message_fragment = LatestUpdateFragmentView(
                ).render_to_fragment(request, course_id=course_id, **kwargs)
            else:
                update_message_fragment = WelcomeMessageFragmentView(
                ).render_to_fragment(request, course_id=course_id, **kwargs)
            course_sock_fragment = CourseSockFragmentView().render_to_fragment(
                request, course=course, **kwargs)
            has_visited_course, resume_course_url, resume_course_title = self._get_resume_course_info(
                request, course_id)
            handouts_html = self._get_course_handouts(request, course)

            offer_banner_fragment = get_first_purchase_offer_banner_fragment(
                request.user, course_overview)
            course_expiration_fragment = generate_course_expired_fragment(
                request.user, course_overview)

            next_up_banner_fragment = NextUpBannerFragmentView(
            ).render_to_fragment(assignment_title=resume_course_title,
                                 resume_course_url=resume_course_url,
                                 assignment_duration='10 min')

        elif allow_public_outline or allow_public:
            outline_fragment = CourseOutlineFragmentView().render_to_fragment(
                request, course_id=course_id, user_is_enrolled=False, **kwargs)
            course_sock_fragment = CourseSockFragmentView().render_to_fragment(
                request, course=course, **kwargs)
            if allow_public:
                handouts_html = self._get_course_handouts(request, course)
        else:
            # Redirect the user to the dashboard if they are not enrolled and
            # this is a course that does not support direct enrollment.
            if not can_self_enroll_in_course(course_key):
                raise CourseAccessRedirect(reverse('dashboard'))

        # Get the course tools enabled for this user and course
        course_tools = CourseToolsPluginManager.get_enabled_course_tools(
            request, course_key)

        # Check if the user can access the course goal functionality
        has_goal_permission = has_course_goal_permission(
            request, course_id, user_access)

        # Grab the current course goal and the acceptable course goal keys mapped to translated values
        current_goal = get_course_goal(request.user, course_key)
        goal_options = get_course_goal_options()

        # Get the course goals api endpoint
        goal_api_url = get_goal_api_url(request)

        # Grab the course home messages fragment to render any relevant django messages
        course_home_message_fragment = CourseHomeMessageFragmentView(
        ).render_to_fragment(request,
                             course_id=course_id,
                             user_access=user_access,
                             **kwargs)

        # Get info for upgrade messaging
        upgrade_price = None
        upgrade_url = None
        has_discount = False

        # TODO Add switch to control deployment
        if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(
                course_key) and can_show_verified_upgrade(
                    request.user, enrollment, course):
            upgrade_url = verified_upgrade_deadline_link(request.user,
                                                         course_id=course_key)
            upgrade_price, has_discount = format_strikeout_price(
                request.user, course_overview)

        show_search = (
            settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH') or
            (settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF')
             and user_access['is_staff']))
        # Render the course home fragment
        context = {
            'request': request,
            'csrf': csrf(request)['csrf_token'],
            'course': course,
            'course_key': course_key,
            'outline_fragment': outline_fragment,
            'handouts_html': handouts_html,
            'course_home_message_fragment': course_home_message_fragment,
            'offer_banner_fragment': offer_banner_fragment,
            'course_expiration_fragment': course_expiration_fragment,
            'next_up_banner_fragment': next_up_banner_fragment,
            'has_visited_course': has_visited_course,
            'resume_course_url': resume_course_url,
            'course_tools': course_tools,
            'dates_fragment': dates_fragment,
            'username': request.user.username,
            'goal_api_url': goal_api_url,
            'has_goal_permission': has_goal_permission,
            'goal_options': goal_options,
            'current_goal': current_goal,
            'update_message_fragment': update_message_fragment,
            'course_sock_fragment': course_sock_fragment,
            'disable_courseware_js': True,
            'uses_bootstrap': True,
            'upgrade_price': upgrade_price,
            'upgrade_url': upgrade_url,
            'has_discount': has_discount,
            'show_search': show_search,
        }
        html = render_to_string('course_experience/course-home-fragment.html',
                                context)
        return Fragment(html)
    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
        self.assertIsNone(cea.user)

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

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

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

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

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

        # The CEA is now linked
        cea.refresh_from_db()
        self.assertEqual(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 self.assertRaises(EnrollmentClosedError):
            CourseEnrollment.enroll(user2, self.course.id, check_access=True)

        # CEA still linked to user1. Also after unenrolling
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

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

        cea.refresh_from_db()
        self.assertEqual(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()
        self.assertEqual(cea.user, user1)