def test_set_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') self.assertFalse(self.service.is_credit_course(no_credit_course.id)) CourseEnrollment.enroll(self.user, no_credit_course.id) # this should be a no-op self.service.set_credit_requirement_status( self.user.id, no_credit_course.id, 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) self.assertEqual(len(credit_state['credit_requirement_status']), 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)
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))
def setUp(self): """ Create a course and user, then log in. """ super(BulkEnrollmentTest, self).setUp() self.view = BulkEnrollView.as_view() self.request_factory = APIRequestFactory() self.url = reverse('bulk_enroll') self.staff = UserFactory.create( username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD, is_staff=True, ) self.course = CourseFactory.create() self.course_key = unicode(self.course.id) self.enrolled_student = UserFactory(username='******', first_name='Enrolled', last_name='Student') CourseEnrollment.enroll( self.enrolled_student, self.course.id ) self.notenrolled_student = UserFactory(username='******', first_name='NotEnrolled', last_name='Student') # Email URL values self.site_name = microsite.get_value( 'SITE_NAME', settings.SITE_NAME ) self.about_path = '/courses/{}/about'.format(self.course.id) self.course_path = '/courses/{}/'.format(self.course.id)
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))
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))
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)
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="******")
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]')
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)
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='******')
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)] )
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)
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()
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)
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"])
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)
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)
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 )
def test_enrolled_in_expired(self): create_mode( self.course, CourseMode.VERIFIED, "Verified Enrollment Track", min_price=1, expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=-1) ) CourseEnrollment.enroll(self.student, self.course.id, mode=CourseMode.VERIFIED) self.assertEqual("Verified Enrollment Track", self._get_user_group().name)
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()
def test_program_courses_with_invalid_data(self, key_remove, log_warn): """Test programs with invalid responses.""" CourseEnrollment.enroll(self.user, self.course_1.id) self.client.login(username="******", password="******") self.create_config() program_data = self._create_program_data([(self.course_1.id, 'active')]) if key_remove and key_remove in program_data[unicode(self.course_1.id)]: del program_data[unicode(self.course_1.id)][key_remove] with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = program_data response = self.client.get(reverse('dashboard')) # if data is invalid then warning log will be recorded. if key_remove: log_warn.assert_called_with( 'Program structure is invalid, skipping display: %r', program_data[ unicode(self.course_1.id) ] ) # verify that no programs related upsell messages appear on the # student dashboard. self._assert_responses(response, 0) else: # in case of valid data all upsell messages will appear on dashboard. self._assert_responses(response, 1) # verify that only normal courses (non-programs courses) appear on # the student dashboard. self.assertContains(response, 'course-container', 1) self.assertIn('Pursue a Certificate of Achievement to highlight', response.content)
def enroll_user(user, course_key): # Activate user registration = world.RegistrationFactory(user=user) registration.register(user) registration.activate() # Enroll them in the course CourseEnrollment.enroll(user, course_key)
def test_retirement(self): """ Tests that calling the retirement method for a specific enrollment retires the enrolled_email and reason columns of each row associated with that enrollment. """ enrollment = CourseEnrollment.enroll(self.user, self.course.id) other_enrollment = CourseEnrollment.enroll(self.user, self.other_course.id) ManualEnrollmentAudit.create_manual_enrollment_audit( self.instructor, self.user.email, ALLOWEDTOENROLL_TO_ENROLLED, 'manually enrolling unenrolled user', enrollment ) ManualEnrollmentAudit.create_manual_enrollment_audit( self.instructor, self.user.email, ALLOWEDTOENROLL_TO_ENROLLED, 'manually enrolling unenrolled user again', enrollment ) ManualEnrollmentAudit.create_manual_enrollment_audit( self.instructor, self.user.email, ALLOWEDTOENROLL_TO_ENROLLED, 'manually enrolling unenrolled user', other_enrollment ) ManualEnrollmentAudit.create_manual_enrollment_audit( self.instructor, self.user.email, ALLOWEDTOENROLL_TO_ENROLLED, 'manually enrolling unenrolled user again', other_enrollment ) self.assertTrue(ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exists()) # retire the ManualEnrollmentAudit objects associated with the above enrollments enrollments = CourseEnrollment.objects.filter(user=self.user) ManualEnrollmentAudit.retire_manual_enrollments(enrollments=enrollments, retired_email="xxx") self.assertTrue(ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exists()) self.assertFalse(ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude( enrolled_email="xxx" )) self.assertFalse(ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude( reason="" ))
def setUp(self): super(TestVariedMetadata, self).setUp() self.course = CourseFactory.create() with self.store.bulk_operations(self.course.id): self.chapter = ItemFactory.create( parent=self.course, category="chapter", display_name="Test Chapter" ) self.sequence = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Test Sequential 1", graded=True ) self.vertical = ItemFactory.create( parent=self.sequence, category='vertical', display_name='Test Vertical 1' ) self.problem_xml = u''' <problem url_name="capa-optionresponse"> <optionresponse> <optioninput options="('Correct', 'Incorrect')" correct="Correct"></optioninput> <optioninput options="('Correct', 'Incorrect')" correct="Correct"></optioninput> </optionresponse> </problem> ''' self.addCleanup(set_current_request, None) self.request = get_mock_request(UserFactory()) self.client.login(username=self.request.user.username, password="******") CourseEnrollment.enroll(self.request.user, self.course.id)
def test_staff_level(self): """ Ensure that a staff member can't access instructor endpoints. """ staff_member = StaffFactory(self.course) CourseEnrollment.enroll(staff_member, self.course.id) self.client.login(username=staff_member.username, password='******') # Try to promote to forums admin - not working # update_forum_role(self.course.id, staff_member, FORUM_ROLE_ADMINISTRATOR, 'allow') for endpoint, args in self.staff_level_endpoints: # TODO: make these work if endpoint in ['update_forum_role_membership', 'proxy_legacy_analytics', 'list_forum_members']: continue self._access_endpoint( endpoint, args, 200, "Staff member should be allowed to access endpoint " + endpoint ) for endpoint, args in self.instructor_level_endpoints: self._access_endpoint( endpoint, args, 403, "Staff member should not be allowed to access endpoint " + endpoint )
def test_instructor_level(self): """ Ensure that an instructor member can access all endpoints. """ inst = InstructorFactory(self.course) CourseEnrollment.enroll(inst, self.course.id) self.client.login(username=inst.username, password='******') for endpoint, args in self.staff_level_endpoints: # TODO: make these work if endpoint in ['update_forum_role_membership', 'proxy_legacy_analytics']: continue self._access_endpoint( endpoint, args, 200, "Instructor should be allowed to access endpoint " + endpoint ) for endpoint, args in self.instructor_level_endpoints: # TODO: make this work if endpoint in ['rescore_problem']: continue self._access_endpoint( endpoint, args, 200, "Instructor should be allowed to access endpoint " + endpoint )
def test_modes_program_courses_on_dashboard_with_configuration(self, course_mode): """Test that if program configuration is enabled than student can only see those courses with xseries upsell messages which are active in xseries programs. """ CourseEnrollment.enroll(self.user, self.course_1.id, mode=course_mode) CourseEnrollment.enroll(self.user, self.course_2.id, mode=course_mode) self.client.login(username="******", password="******") self.create_config() with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = self._create_program_data( [(self.course_1.id, 'active'), (self.course_2.id, 'unpublished')] ) response = self.client.get(reverse('dashboard')) # count total courses appearing on student dashboard self.assertContains(response, 'course-container', 2) self._assert_responses(response, 1) # for verified enrollment view the program detail button will have # the class 'base-btn' # for other modes view the program detail button will have have the # class border-btn if course_mode == 'verified': self.assertIn('xseries-base-btn', response.content) else: self.assertIn('xseries-border-btn', response.content)
def test_activation(self): user = User.objects.create(username="******", email="*****@*****.**") course_id = "edX/Test101/2013" self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) # Creating an enrollment doesn't actually enroll a student # (calling CourseEnrollment.enroll() would have) enrollment = CourseEnrollment.create_enrollment(user, course_id) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) # Until you explicitly activate it enrollment.activate() self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) # Activating something that's already active does nothing enrollment.activate() self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) # Now deactive enrollment.deactivate() self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) # Deactivating something that's already inactive does nothing enrollment.deactivate() self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) # A deactivated enrollment should be activated if enroll() is called # for that user/course_id combination CourseEnrollment.enroll(user, course_id) self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
def setUp(self): super(LmsSearchFilterGeneratorTestCase, self).setUp() self.build_courses() self.user = UserFactory.create(username="******", email="*****@*****.**", password='******') for course in self.courses: CourseEnrollment.enroll(self.user, course.location.course_key)
def setUp(self): super(TestCourseGradeLogging, self).setUp() self.course = CourseFactory.create() with self.store.bulk_operations(self.course.id): self.chapter = ItemFactory.create( parent=self.course, category="chapter", display_name="Test Chapter" ) self.sequence = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Test Sequential 1", graded=True ) self.sequence_2 = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Test Sequential 2", graded=True ) self.sequence_3 = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Test Sequential 3", graded=False ) self.vertical = ItemFactory.create( parent=self.sequence, category='vertical', display_name='Test Vertical 1' ) self.vertical_2 = ItemFactory.create( parent=self.sequence_2, category='vertical', display_name='Test Vertical 2' ) self.vertical_3 = ItemFactory.create( parent=self.sequence_3, category='vertical', display_name='Test Vertical 3' ) problem_xml = MultipleChoiceResponseXMLFactory().build_xml( question_text='The correct answer is Choice 2', choices=[False, False, True, False], choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3'] ) self.problem = ItemFactory.create( parent=self.vertical, category="problem", display_name="test_problem_1", data=problem_xml ) self.problem_2 = ItemFactory.create( parent=self.vertical_2, category="problem", display_name="test_problem_2", data=problem_xml ) self.problem_3 = ItemFactory.create( parent=self.vertical_3, category="problem", display_name="test_problem_3", data=problem_xml ) self.request = get_mock_request(UserFactory()) self.client.login(username=self.request.user.username, password="******") self.course_structure = get_course_blocks(self.request.user, self.course.location) self.subsection_grade_factory = SubsectionGradeFactory(self.request.user, self.course, self.course_structure) CourseEnrollment.enroll(self.request.user, self.course.id)
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()
def test_no_upgrade_message_if_verified_track(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) self.assert_upgrade_message_not_displayed()
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)
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()
def setUpTestData(cls): """Set up and enroll our fake user in the course.""" 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)
def enroll(self, course_id=None): """Enroll test user in test course.""" CourseEnrollment.enroll(self.user, course_id or self.course.id)
def setUp(self): """ Test case scaffolding """ super(EntranceExamTestCases, self).setUp() self.course = CourseFactory.create( metadata={ 'entrance_exam_enabled': True, } ) with self.store.bulk_operations(self.course.id): self.chapter = ItemFactory.create( parent=self.course, display_name='Overview' ) self.welcome = ItemFactory.create( parent=self.chapter, display_name='Welcome' ) ItemFactory.create( parent=self.course, category='chapter', display_name="Week 1" ) self.chapter_subsection = ItemFactory.create( parent=self.chapter, category='sequential', display_name="Lesson 1" ) chapter_vertical = ItemFactory.create( parent=self.chapter_subsection, category='vertical', display_name='Lesson 1 Vertical - Unit 1' ) ItemFactory.create( parent=chapter_vertical, category="problem", display_name="Problem - Unit 1 Problem 1" ) ItemFactory.create( parent=chapter_vertical, category="problem", display_name="Problem - Unit 1 Problem 2" ) ItemFactory.create( category="instructor", parent=self.course, data="Instructor Tab", display_name="Instructor" ) self.entrance_exam = ItemFactory.create( parent=self.course, category="chapter", display_name="Entrance Exam Section - Chapter 1", is_entrance_exam=True, in_entrance_exam=True ) self.exam_1 = ItemFactory.create( parent=self.entrance_exam, category='sequential', display_name="Exam Sequential - Subsection 1", graded=True, in_entrance_exam=True ) subsection = ItemFactory.create( parent=self.exam_1, category='vertical', display_name='Exam Vertical - Unit 1' ) problem_xml = MultipleChoiceResponseXMLFactory().build_xml( question_text='The correct answer is Choice 3', choices=[False, False, True, False], choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3'] ) self.problem_1 = ItemFactory.create( parent=subsection, category="problem", display_name="Exam Problem - Problem 1", data=problem_xml ) self.problem_2 = ItemFactory.create( parent=subsection, category="problem", display_name="Exam Problem - Problem 2" ) add_entrance_exam_milestone(self.course, self.entrance_exam) self.course.entrance_exam_enabled = True self.course.entrance_exam_minimum_score_pct = 0.50 self.course.entrance_exam_id = unicode(self.entrance_exam.scope_ids.usage_id) self.anonymous_user = AnonymousUserFactory() self.addCleanup(set_current_request, None) self.request = get_mock_request(UserFactory()) modulestore().update_item(self.course, self.request.user.id) self.client.login(username=self.request.user.username, password="******") CourseEnrollment.enroll(self.request.user, self.course.id) self.expected_locked_toc = ( [ { 'active': True, 'sections': [ { 'url_name': u'Exam_Sequential_-_Subsection_1', 'display_name': u'Exam Sequential - Subsection 1', 'graded': True, 'format': '', 'due': None, 'active': True } ], 'url_name': u'Entrance_Exam_Section_-_Chapter_1', 'display_name': u'Entrance Exam Section - Chapter 1', 'display_id': u'entrance-exam-section-chapter-1', } ] ) self.expected_unlocked_toc = ( [ { 'active': False, 'sections': [ { 'url_name': u'Welcome', 'display_name': u'Welcome', 'graded': False, 'format': '', 'due': None, 'active': False }, { 'url_name': u'Lesson_1', 'display_name': u'Lesson 1', 'graded': False, 'format': '', 'due': None, 'active': False } ], 'url_name': u'Overview', 'display_name': u'Overview', 'display_id': u'overview' }, { 'active': False, 'sections': [], 'url_name': u'Week_1', 'display_name': u'Week 1', 'display_id': u'week-1' }, { 'active': False, 'sections': [], 'url_name': u'Instructor', 'display_name': u'Instructor', 'display_id': u'instructor' }, { 'active': True, 'sections': [ { 'url_name': u'Exam_Sequential_-_Subsection_1', 'display_name': u'Exam Sequential - Subsection 1', 'graded': True, 'format': '', 'due': None, 'active': True } ], 'url_name': u'Entrance_Exam_Section_-_Chapter_1', 'display_name': u'Entrance Exam Section - Chapter 1', 'display_id': u'entrance-exam-section-chapter-1' } ] )
def create_new_course(request): """ Create a new course. Returns the URL for the course overview page. """ if not auth.has_access(request.user, CourseCreatorRole()): raise PermissionDenied() org = request.json.get('org') number = request.json.get('number') display_name = request.json.get('display_name') run = request.json.get('run') try: dest_location = Location(u'i4x', org, number, u'course', run) except InvalidLocationError as error: return JsonResponse({ "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format( name=display_name, err=error.message) }) # see if the course already exists existing_course = None try: existing_course = modulestore('direct').get_item(dest_location) except ItemNotFoundError: pass if existing_course is not None: return JsonResponse({ 'ErrMsg': _('There is already a course defined with the same ' 'organization, course number, and course run. Please ' 'change either organization or course number to be ' 'unique.'), 'OrgErrMsg': _('Please change either the organization or ' 'course number so that it is unique.'), 'CourseErrMsg': _('Please change either the organization or ' 'course number so that it is unique.'), }) # dhm: this query breaks the abstraction, but I'll fix it when I do my suspended refactoring of this # file for new locators. get_items should accept a query rather than requiring it be a legal location course_search_location = bson.son.SON({ '_id.tag': 'i4x', # cannot pass regex to Location constructor; thus this hack # pylint: disable=E1101 '_id.org': re.compile(u'^{}$'.format(dest_location.org), re.IGNORECASE | re.UNICODE), # pylint: disable=E1101 '_id.course': re.compile(u'^{}$'.format(dest_location.course), re.IGNORECASE | re.UNICODE), '_id.category': 'course', }) courses = modulestore().collection.find(course_search_location, fields=('_id')) if courses.count() > 0: return JsonResponse({ 'ErrMsg': _('There is already a course defined with the same ' 'organization and course number. Please ' 'change at least one field to be unique.'), 'OrgErrMsg': _('Please change either the organization or ' 'course number so that it is unique.'), 'CourseErrMsg': _('Please change either the organization or ' 'course number so that it is unique.'), }) # instantiate the CourseDescriptor and then persist it # note: no system to pass if display_name is None: metadata = {} else: metadata = {'display_name': display_name} modulestore('direct').create_and_save_xmodule(dest_location, metadata=metadata) new_course = modulestore('direct').get_item(dest_location) # clone a default 'about' overview module as well dest_about_location = dest_location.replace(category='about', name='overview') overview_template = AboutDescriptor.get_template('overview.yaml') modulestore('direct').create_and_save_xmodule( dest_about_location, system=new_course.system, definition_data=overview_template.get('data')) initialize_course_tabs(new_course) new_location = loc_mapper().translate_location( new_course.location.course_id, new_course.location, False, True) # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course # however, we can assume that b/c this user had authority to create the course, the user can add themselves CourseInstructorRole(new_location).add_users(request.user) auth.add_users(request.user, CourseStaffRole(new_location), request.user) # seed the forums seed_permissions_roles(new_course.location.course_id) # auto-enroll the course creator in the course so that "View Live" will # work. CourseEnrollment.enroll(request.user, new_course.location.course_id) return JsonResponse({'url': new_location.url_reverse("course/", "")})
def post(self, request, course_id): """Takes the form submission from the page and parses it. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Returns: Status code 400 when the requested mode is unsupported. When the honor mode is selected, redirects to the dashboard. When the verified mode is selected, returns error messages if the indicated contribution amount is invalid or below the minimum, otherwise redirects to the verification flow. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) user = request.user # This is a bit redundant with logic in student.views.change_enrollment, # but I don't really have the time to refactor it more nicely and test. course = modulestore().get_course(course_key) if not has_access(user, 'enroll', course): error_msg = _("Enrollment is closed") return self.get(request, course_id, error=error_msg) requested_mode = self._get_requested_mode(request.POST) allowed_modes = CourseMode.modes_for_course_dict(course_key) if requested_mode not in allowed_modes: return HttpResponseBadRequest(_("Enrollment mode not supported")) if requested_mode == 'audit': # The user will have already been enrolled in the audit mode at this # point, so we just redirect them to the dashboard, thereby avoiding # hitting the database a second time attempting to enroll them. return redirect(reverse('dashboard')) if requested_mode == 'honor': CourseEnrollment.enroll(user, course_key, mode=requested_mode) return redirect(get_course_success_page_url(course_id)) mode_info = allowed_modes[requested_mode] if requested_mode == 'verified': amount = request.POST.get("contribution") or \ request.POST.get("contribution-other-amt") or 0 try: # Validate the amount passed in and force it into two digits amount_value = decimal.Decimal(amount).quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: error_msg = _("Invalid amount selected.") return self.get(request, course_id, error=error_msg) # Check for minimum pricing if amount_value < mode_info.min_price: error_msg = _( "No selected price or selected price is too low.") return self.get(request, course_id, error=error_msg) donation_for_course = request.session.get("donation_for_course", {}) donation_for_course[unicode(course_key)] = amount_value request.session["donation_for_course"] = donation_for_course return redirect( reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)}))
def test_look_up_valid_registration_code(self): """ test lookup for the valid registration code and that registration code has been redeemed by user and then mark the registration code as in_valid when marking as invalidate, it also lookup for registration redemption entry and also delete that redemption entry and un_enroll the student who used that registration code for their enrollment. """ for i in range(2): CourseRegistrationCode.objects.create( code='reg_code{}'.format(i), course_id=six.text_type(self.course.id), created_by=self.instructor, invoice=self.sale_invoice, invoice_item=self.invoice_item, mode_slug=CourseMode.DEFAULT_MODE_SLUG ) reg_code = CourseRegistrationCode.objects.all()[0] student = UserFactory() enrollment = CourseEnrollment.enroll(student, self.course.id) RegistrationCodeRedemption.objects.create( registration_code=reg_code, redeemed_by=student, course_enrollment=enrollment ) data = { 'registration_code': reg_code.code } response = self.client.get(self.lookup_code_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) self.assertTrue(json_dict['is_registration_code_valid']) self.assertTrue(json_dict['is_registration_code_redeemed']) # now mark that registration code as invalid data = { 'registration_code': reg_code.code, 'action_type': 'invalidate_registration_code' } response = self.client.post(self.registration_code_detail_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) message = _('This enrollment code has been canceled. It can no longer be used.') self.assertEqual(message, json_dict['message']) # now check that the registration code should be marked as invalid in the db. reg_code = CourseRegistrationCode.objects.get(code=reg_code.code) self.assertEqual(reg_code.is_valid, False) redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id) self.assertIsNone(redemption) # now the student course enrollment should be false. enrollment = CourseEnrollment.get_enrollment(student, self.course.id) self.assertEqual(enrollment.is_active, False)
def setUp(self): self.user = UserFactory.create() self.course = CourseFactory.create() CourseEnrollment.enroll(self.user, self.course.id) self.client.login(username=self.user.username, password='******')
def setUp(self): super(ComputeGradesForCourseTest, self).setUp() self.users = [UserFactory.create() for _ in range(12)] self.set_up_course() for user in self.users: CourseEnrollment.enroll(user, self.course.id)
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)
def test_offer_html(self): CourseEnrollment.enroll(self.user, self.course.id) self.assertEqual(self.client.get(self.url).data['offer_html'], '<p>Offer</p>')
def setUp(self): super(RecalculateGradesForUserTest, self).setUp() self.user = UserFactory.create() self.set_up_course() CourseEnrollment.enroll(self.user, self.course.id)
def test_handouts(self): CourseEnrollment.enroll(self.user, self.course.id) self.store.create_item(self.user.id, self.course.id, 'course_info', 'handouts', fields={'data': '<p>Hi</p>'}) self.assertEqual(self.client.get(self.url).data['handouts_html'], '<p>Hi</p>')
def test_course_expired_html(self): CourseEnrollment.enroll(self.user, self.course.id) self.assertEqual(self.client.get(self.url).data['course_expired_html'], '<p>Expired</p>')
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)
def test_waffle_flag_disabled(self, enrollment_mode): CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode) response = self.client.get(self.url) self.assertEqual(response.status_code, 404)
def test_refund_cert_no_cert_exists(self): # If there is no paid certificate, the refund callback should return nothing CourseEnrollment.enroll(self.user, self.course_key, 'verified') ret_val = CourseEnrollment.unenroll(self.user, self.course_key) self.assertFalse(ret_val)
def test_transfer_students(self): """ Verify the transfer student command works as intended. """ student = UserFactory.create() student.set_password(self.PASSWORD) student.save() mode = 'verified' # Original Course original_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0') course = self._create_course(original_course_location) # Enroll the student in 'verified' CourseEnrollment.enroll(student, course.id, mode="verified") # Create and purchase a verified cert for the original course. self._create_and_purchase_verified(student, course.id) # New Course 1 course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1') new_course_one = self._create_course(course_location_one) # New Course 2 course_location_two = locator.CourseLocator('Org2', 'Course2', 'Run2') new_course_two = self._create_course(course_location_two) original_key = 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=maybe-no-member [ 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')
def setUp(self): super(TeamAPITestCase, self).setUp() self.topics_count = 4 self.users = { 'staff': AdminFactory.create(password=self.test_password), 'course_staff': StaffFactory.create(course_key=self.test_course_1.id, password=self.test_password) } self.create_and_enroll_student(username='******') self.create_and_enroll_student(username='******') self.create_and_enroll_student(username='******', courses=[]) # Make this student a community TA. self.create_and_enroll_student(username='******') seed_permissions_roles(self.test_course_1.id) community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=self.test_course_1.id) community_ta_role.users.add(self.users['community_ta']) # 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. self.create_and_enroll_student( courses=[self.test_course_1, self.test_course_2], username='******') # Make this student have a public profile self.create_and_enroll_student( courses=[self.test_course_2], username='******') profile = self.users['student_enrolled_public_profile'].profile profile.year_of_birth = 1970 profile.save() # '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) self.test_team_6 = CourseTeamFactory.create( name='Public Profile Team', course_id=self.test_course_2.id, topic_id='topic_6') self.test_team_name_id_map = { team.name: team for team in ( self.test_team_1, self.test_team_2, self.test_team_3, self.test_team_4, self.test_team_5, ) } for user, course in [('staff', self.test_course_1), ('course_staff', self.test_course_1)]: 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']) self.test_team_6.add_user( self.users['student_enrolled_public_profile'])
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 auto_auth(request): """ Create or configure a user account, then log in as that user. Enabled only when settings.FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] is true. Accepts the following querystring parameters: * `username`, `email`, and `password` for the user account * `full_name` for the user profile (the user's full name; defaults to the username) * `staff`: Set to "true" to make the user global staff. * `course_id`: Enroll the student in the course with `course_id` * `roles`: Comma-separated list of roles to grant the student in the course with `course_id` * `no_login`: Define this to create the user but not login * `redirect`: Set to "true" will redirect to the `redirect_to` value if set, or course home page if course_id is defined, otherwise it will redirect to dashboard * `redirect_to`: will redirect to to this url * `is_active` : make/update account with status provided as 'is_active' If username, email, or password are not provided, use randomly generated credentials. """ # Generate a unique name to use if none provided generated_username = uuid.uuid4().hex[0:30] generated_password = generate_password() # Use the params from the request, otherwise use these defaults username = request.GET.get('username', generated_username) password = request.GET.get('password', generated_password) email = request.GET.get('email', username + "@example.com") full_name = request.GET.get('full_name', username) is_staff = str2bool(request.GET.get('staff', False)) is_superuser = str2bool(request.GET.get('superuser', False)) course_id = request.GET.get('course_id') redirect_to = request.GET.get('redirect_to') is_active = str2bool(request.GET.get('is_active', True)) # Valid modes: audit, credit, honor, no-id-professional, professional, verified enrollment_mode = request.GET.get('enrollment_mode', 'honor') # Parse roles, stripping whitespace, and filtering out empty strings roles = _clean_roles(request.GET.get('roles', '').split(',')) course_access_roles = _clean_roles( request.GET.get('course_access_roles', '').split(',')) redirect_when_done = str2bool(request.GET.get('redirect', '')) or redirect_to login_when_done = 'no_login' not in request.GET form = AccountCreationForm(data={ 'username': username, 'email': email, 'password': password, 'name': full_name, }, tos_required=False) # Attempt to create the account. # If successful, this will return a tuple containing # the new user object. try: user, profile, reg = do_create_account(form) except (AccountValidationError, ValidationError): # Attempt to retrieve the existing user. user = User.objects.get(username=username) user.email = email user.set_password(password) user.is_active = is_active user.save() profile = UserProfile.objects.get(user=user) reg = Registration.objects.get(user=user) except PermissionDenied: return HttpResponseForbidden(_('Account creation not allowed.')) user.is_staff = is_staff user.is_superuser = is_superuser user.save() if is_active: reg.activate() reg.save() # ensure parental consent threshold is met year = datetime.date.today().year age_limit = settings.PARENTAL_CONSENT_AGE_LIMIT profile.year_of_birth = (year - age_limit) - 1 profile.save() create_or_set_user_attribute_created_on_site(user, request.site) # Enroll the user in a course course_key = None if course_id: course_key = CourseLocator.from_string(course_id) CourseEnrollment.enroll(user, course_key, mode=enrollment_mode) # Apply the roles for role in roles: assign_role(course_key, user, role) for role in course_access_roles: CourseAccessRole.objects.update_or_create(user=user, course_id=course_key, org=course_key.org, role=role) # Log in as the user if login_when_done: user = authenticate_new_user(request, username, password) django_login(request, user) create_comments_service_user(user) if redirect_when_done: if redirect_to: # Redirect to page specified by the client redirect_url = redirect_to elif course_id: # Redirect to the course homepage (in LMS) or outline page (in Studio) try: redirect_url = reverse(course_home_url_name(course_key), kwargs={'course_id': course_id}) except NoReverseMatch: redirect_url = reverse('course_handler', kwargs={'course_key_string': course_id}) else: # Redirect to the learner dashboard (in LMS) or homepage (in Studio) try: redirect_url = reverse('dashboard') except NoReverseMatch: redirect_url = reverse('home') return redirect(redirect_url) else: response = JsonResponse({ 'created_status': 'Logged in' if login_when_done else 'Created', 'username': username, 'email': email, 'password': password, 'user_id': user.id, # pylint: disable=no-member 'anonymous_id': anonymous_id_for_user(user, None), }) response.set_cookie('csrftoken', csrf(request)['csrf_token']) return response
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"))
def test_add_course_to_cart_already_registered(self): CourseEnrollment.enroll(self.user, self.course_key) self.login_user() resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()])) self.assertEqual(resp.status_code, 400) self.assertIn(_('You are already registered in course {0}.'.format(self.course_key.to_deprecated_string())), resp.content)
def test_unenroll(self): CourseEnrollment.enroll(self.student, self.course.id) self.update_enrollement("unenroll", self.student.email) self.check_outbox_is_french()
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()
def create_new_course(request): """ Create a new course. Returns the URL for the course overview page. """ if not auth.has_access(request.user, CourseCreatorRole()): raise PermissionDenied() org = request.json.get('org') number = request.json.get('number') display_name = request.json.get('display_name') run = request.json.get('run') # allow/disable unicode characters in course_id according to settings if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'): if _has_non_ascii_characters(org) or _has_non_ascii_characters(number) or _has_non_ascii_characters(run): return JsonResponse( {'error': _('Special characters not allowed in organization, course number, and course run.')}, status=400 ) try: course_key = SlashSeparatedCourseKey(org, number, run) # instantiate the CourseDescriptor and then persist it # note: no system to pass if display_name is None: metadata = {} else: metadata = {'display_name': display_name} # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for # existing xml courses this cannot be changed in CourseDescriptor. # # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile # w/ xmodule.course_module.CourseDescriptor.__init__ wiki_slug = u"{0}.{1}.{2}".format(course_key.org, course_key.course, course_key.run) definition_data = {'wiki_slug': wiki_slug} # Create the course then fetch it from the modulestore # Check if role permissions group for a course named like this already exists # Important because role groups are case insensitive if CourseRole.course_group_already_exists(course_key): raise InvalidLocationError() fields = {} fields.update(definition_data) fields.update(metadata) # Creating the course raises InvalidLocationError if an existing course with this org/name is found new_course = modulestore('direct').create_course( course_key.org, course_key.offering, fields=fields, ) # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course # however, we can assume that b/c this user had authority to create the course, the user can add themselves CourseInstructorRole(new_course.id).add_users(request.user) auth.add_users(request.user, CourseStaffRole(new_course.id), request.user) # seed the forums seed_permissions_roles(new_course.id) # auto-enroll the course creator in the course so that "View Live" will # work. CourseEnrollment.enroll(request.user, new_course.id) _users_assign_default_role(new_course.id) #os.system('python /home/rajarshi/trigger/trigger_test.py') return JsonResponse({ 'url': reverse_course_url('course_handler', new_course.id) }) except InvalidLocationError: return JsonResponse({ 'ErrMsg': _( 'There is already a course defined with the same ' 'organization, course number, and course run. Please ' 'change either organization or course number to be unique.' ), 'OrgErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.'), 'CourseErrMsg': _( 'Please change either the organization or ' 'course number so that it is unique.'), }) except InvalidKeyError as error: return JsonResponse({ "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(name=display_name, err=error.message)} )