def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_remove_master_course_staff_from_ccx_display_name(self): """ Test remove role of staff of master course on ccx course. Specific test to check that a passed display name is in the subject of the email sent to the unenrolled users. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) # create a unique display name display_name = 'custom_display_{}'.format(uuid.uuid4()) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') self.assertEqual(len(outbox), 0) # give access to the course staff/instructor remove_master_course_staff_from_ccx(self.course, self.ccx_locator, display_name) self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: self.assertIn(display_name, email.subject)
def test_add_master_course_staff_to_ccx_display_name(self): """ Test add staff of master course to ccx course. Specific test to check that a passed display name is in the subject of the email sent to the enrolled users. """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) outbox = self.get_outbox() # create a unique display name display_name = f'custom_display_{uuid.uuid4()}' list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level( self.course.id, 'instructor') assert len(outbox) == 0 # give access to the course staff/instructor add_master_course_staff_to_ccx(self.course, self.ccx_locator, display_name) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: assert display_name in email.subject
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ self.make_coach() ccx = self.make_ccx() ccx_locator = CCXLocator.from_course_locator(self.course.id, ccx.id) add_master_course_staff_to_ccx(self.course, ccx_locator, ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') with ccx_course(ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) outbox = self.get_outbox() assert len(outbox) == 0 add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level(self.course.id, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) == len(list_staff_ccx_course) assert list_staff_master_course[0].email == list_staff_ccx_course[0].email list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) == len(list_instructor_master_course) assert list_instructor_ccx_course[0].email == list_instructor_master_course[0].email # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True): """ Remove staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseBlockWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on revoke access. """ list_staff = list_with_level(master_course.id, 'staff') list_instructor = list_with_level(master_course.id, 'instructor') with ccx_course(ccx_key) as course_ccx: list_staff_ccx = list_with_level(course_ccx.id, 'staff') list_instructor_ccx = list_with_level(course_ccx.id, 'instructor') email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: if staff in list_staff_ccx: # revoke 'staff' access on ccx. revoke_access(course_ccx, staff, 'staff') # Unenroll the staff on ccx. unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=send_email, email_params=email_params, ) for instructor in list_instructor: if instructor in list_instructor_ccx: # revoke 'instructor' access on ccx. revoke_access(course_ccx, instructor, 'instructor') # Unenroll the instructor on ccx. unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=send_email, email_params=email_params, )
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course) # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def generate_user_certificates(student, course_key, insecure=False, generation_mode='batch', forced_grade=None): """ It will add the add-cert request into the xqueue. A new record will be created to track the certificate generation task. If an error occurs while adding the certificate to the queue, the task will have status 'error'. It also emits `edx.certificate.created` event for analytics. This method has not yet been updated (it predates the certificates revamp). If modifying this method, see also generate_user_certificates() in generation_handler.py (which is very similar but is not called from a celery task). In the future these methods will be unified. Args: student (User) course_key (CourseKey) Keyword Arguments: insecure - (Boolean) generation_mode - who has requested certificate generation. Its value should `batch` in case of django command and `self` if student initiated the request. forced_grade - a string indicating to replace grade parameter. if present grading will be skipped. """ beta_testers_queryset = list_with_level(course_key, 'beta') if beta_testers_queryset.filter(username=student.username): log.info(f"Canceling Certificate Generation task for user {student.id} : {course_key}. User is a Beta Tester.") return xqueue = XQueueCertInterface() if insecure: xqueue.use_https = False course_overview = get_course_overview(course_key) generate_pdf = not has_html_certificates_enabled(course_overview) cert = xqueue.add_cert( student, course_key, generate_pdf=generate_pdf, forced_grade=forced_grade ) log.info(f"Queued Certificate Generation task for {student.id} : {course_key}") # If cert_status is not present in certificate valid_statuses (for example unverified) then # add_cert returns None and raises AttributeError while accessing cert attributes. if cert is None: return if CertificateStatuses.is_passing_status(cert.status): emit_certificate_event('created', student, course_key, course_overview, { 'user_id': student.id, 'course_id': str(course_key), 'certificate_id': cert.verify_uuid, 'enrollment_mode': cert.mode, 'generation_mode': generation_mode }) return cert.status
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True): """ Remove staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseDescriptorWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on revoke access. """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: list_staff_ccx = list_with_level(course_ccx, 'staff') list_instructor_ccx = list_with_level(course_ccx, 'instructor') email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: if staff in list_staff_ccx: # revoke 'staff' access on ccx. revoke_access(course_ccx, staff, 'staff') # Unenroll the staff on ccx. unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=send_email, email_params=email_params, ) for instructor in list_instructor: if instructor in list_instructor_ccx: # revoke 'instructor' access on ccx. revoke_access(course_ccx, instructor, 'instructor') # Unenroll the instructor on ccx. unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=send_email, email_params=email_params, )
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ self.make_coach() ccx = self.make_ccx() ccx_locator = CCXLocator.from_course_locator(self.course.id, ccx.id) add_master_course_staff_to_ccx(self.course, ccx_locator, ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_add_master_course_staff_to_ccx_display_name(self): """ Test add staff of master course to ccx course. Specific test to check that a passed display name is in the subject of the email sent to the enrolled users. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() # create a unique display name display_name = 'custom_display_{}'.format(uuid.uuid4()) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') self.assertEqual(len(outbox), 0) # give access to the course staff/instructor add_master_course_staff_to_ccx(self.course, self.ccx_locator, display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: self.assertIn(display_name, email.subject)
def test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
def test_post_list_staff_master_course_in_ccx(self): """ Specific test to check that the staff and instructor of the master course are assigned to the CCX. """ outbox = self.get_outbox() data = { 'master_course_id': self.master_course_key_str, 'max_students_allowed': 111, 'display_name': 'CCX Test Title', 'coach_email': self.coach.email } resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth) assert resp.status_code == status.HTTP_201_CREATED # check that only one email has been sent and it is to to the coach assert len(outbox) == 1 assert self.coach.email in outbox[0].recipients() list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level(self.course.id, 'instructor') course_key = CourseKey.from_string(resp.data.get('ccx_course_id')) with ccx_course_cm(course_key) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') # The "Coach" in the parent course becomes "Staff" on the CCX, so the CCX should have 1 "Staff" # user more than the parent course assert (len(list_staff_master_course) + 1) == len(list_staff_ccx_course) # Make sure all of the existing course staff are passed to the CCX for course_user in list_staff_master_course: assert course_user in list_staff_ccx_course # Make sure the "Coach" on the parent course is "Staff" on the CCX assert self.coach in list_staff_ccx_course assert len(list_instructor_master_course) == len(list_instructor_ccx_course) for course_user, ccx_user in zip(sorted(list_instructor_master_course), sorted(list_instructor_ccx_course)): assert course_user == ccx_user
def test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
def test_remove_master_course_staff_from_ccx(self): """ Test remove staff of master course to ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, "staff") list_instructor_master_course = list_with_level(self.course, "instructor") with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, "staff") self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, "instructor") self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_ccx_course = list_with_level(course_ccx, "staff") self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, "instructor") self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def test_list_beta(self): beta_testers = list_with_level(self.course, 'beta') assert set(beta_testers) == set(self.beta_testers)
def test_create_ccx(self, ccx_name='New CCX'): """ Create CCX. Follow redirect to coach dashboard, confirm we see the coach dashboard for the new CCX. """ self.make_coach() url = reverse( 'create_ccx', kwargs={'course_id': str(self.course.id)}) response = self.client.post(url, {'name': ccx_name}) assert response.status_code == 302 url = response.get('location') response = self.client.get(url) assert response.status_code == 200 # Get the ccx_key path = six.moves.urllib.parse.urlparse(url).path resolver = resolve(path) ccx_key = resolver.kwargs['course_id'] course_key = CourseKey.from_string(ccx_key) assert CourseEnrollment.is_enrolled(self.coach, course_key) assert re.search('id="ccx-schedule"', response.content.decode('utf-8')) # check if the max amount of student that can be enrolled has been overridden ccx = CustomCourseForEdX.objects.get() course_enrollments = get_override_for_ccx(ccx, self.course, 'max_student_enrollments_allowed') assert course_enrollments == settings.CCX_MAX_STUDENTS_ALLOWED # check if the course display name is properly set course_display_name = get_override_for_ccx(ccx, self.course, 'display_name') assert course_display_name == ccx_name # check if the course display name is properly set in modulestore course_display_name = self.mstore.get_course(ccx.locator).display_name assert course_display_name == ccx_name # assert ccx creator has role=staff role = CourseStaffRole(course_key) assert role.has_user(self.coach) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') # assert that forum roles are seeded assert are_permissions_roles_seeded(course_key) assert has_forum_access(self.coach.username, course_key, FORUM_ROLE_ADMINISTRATOR) with ccx_course(course_key) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') # The "Coach" in the parent course becomes "Staff" on the CCX, so the CCX should have 1 "Staff" # user more than the parent course assert (len(list_staff_master_course) + 1) == len(list_staff_ccx_course) assert list_staff_master_course[0].email in [ccx_staff.email for ccx_staff in list_staff_ccx_course] # Make sure the "Coach" on the parent course is "Staff" on the CCX assert self.coach in list_staff_ccx_course list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') assert len(list_instructor_ccx_course) == len(list_instructor_master_course) assert list_instructor_ccx_course[0].email == list_instructor_master_course[0].email
def course_info_to_ccxcon(course_key): """ Function that gathers informations about the course and makes a post request to a CCXCon with the data. Args: course_key (CourseLocator): the master course key """ try: course = get_course_by_id(course_key) except Http404: log.error('Master Course with key "%s" not found', unicode(course_key)) return if not course.enable_ccx: log.debug('ccx not enabled for course key "%s"', unicode(course_key)) return if not course.ccx_connector: log.debug('ccx connector not defined for course key "%s"', unicode(course_key)) return if not is_valid_url(course.ccx_connector): log.error( 'ccx connector URL "%s" for course key "%s" is not a valid URL.', course.ccx_connector, unicode(course_key) ) return # get the oauth credential for this URL try: ccxcon = CCXCon.objects.get(url=course.ccx_connector) except CCXCon.DoesNotExist: log.error('ccx connector Oauth credentials not configured for URL "%s".', course.ccx_connector) return # get an oauth client with a valid token oauth_ccxcon = get_oauth_client( server_token_url=urlparse.urljoin(course.ccx_connector, CCXCON_TOKEN_URL), client_id=ccxcon.oauth_client_id, client_secret=ccxcon.oauth_client_secret ) # get the entire list of instructors course_instructors = list_with_level(course, 'instructor') # get anonymous ids for each of them course_instructors_ids = [anonymous_id_for_user(user, course_key) for user in course_instructors] # extract the course details course_details = CourseDetails.fetch(course_key) payload = { 'course_id': unicode(course_key), 'title': course.display_name, 'author_name': None, 'overview': course_details.overview, 'description': course_details.short_description, 'image_url': course_details.course_image_asset_path, 'instructors': course_instructors_ids } headers = {'content-type': 'application/json'} # make the POST request add_course_url = urlparse.urljoin(course.ccx_connector, CCXCON_COURSEXS_URL) resp = oauth_ccxcon.post( url=add_course_url, json=payload, headers=headers, timeout=CCXCON_REQUEST_TIMEOUT ) if resp.status_code >= 500: raise CCXConnServerError('Server returned error Status: %s, Content: %s', resp.status_code, resp.content) if resp.status_code >= 400: log.error("Error creating course on ccxcon. Status: %s, Content: %s", resp.status_code, resp.content) # this API performs a POST request both for POST and PATCH, but the POST returns 201 and the PATCH returns 200 elif resp.status_code != HTTP_200_OK and resp.status_code != HTTP_201_CREATED: log.error('Server returned unexpected status code %s', resp.status_code) else: log.debug('Request successful. Status: %s, Content: %s', resp.status_code, resp.content)
def course_info_to_ccxcon(course_key): """ Function that gathers informations about the course and makes a post request to a CCXCon with the data. Args: course_key (CourseLocator): the master course key """ try: course = get_course_by_id(course_key) except Http404: log.error('Master Course with key "%s" not found', unicode(course_key)) return if not course.enable_ccx: log.debug('ccx not enabled for course key "%s"', unicode(course_key)) return if not course.ccx_connector: log.debug('ccx connector not defined for course key "%s"', unicode(course_key)) return if not is_valid_url(course.ccx_connector): log.error( 'ccx connector URL "%s" for course key "%s" is not a valid URL.', course.ccx_connector, unicode(course_key)) return # get the oauth credential for this URL try: ccxcon = CCXCon.objects.get(url=course.ccx_connector) except CCXCon.DoesNotExist: log.error( 'ccx connector Oauth credentials not configured for URL "%s".', course.ccx_connector) return # get an oauth client with a valid token oauth_ccxcon = get_oauth_client(server_token_url=urlparse.urljoin( course.ccx_connector, CCXCON_TOKEN_URL), client_id=ccxcon.oauth_client_id, client_secret=ccxcon.oauth_client_secret) # get the entire list of instructors course_instructors = list_with_level(course, 'instructor') # get anonymous ids for each of them course_instructors_ids = [ anonymous_id_for_user(user, course_key) for user in course_instructors ] # extract the course details course_details = CourseDetails.fetch(course_key) payload = { 'course_id': unicode(course_key), 'title': course.display_name, 'author_name': None, 'overview': course_details.overview, 'description': course_details.short_description, 'image_url': course_details.course_image_asset_path, 'instructors': course_instructors_ids } headers = {'content-type': 'application/json'} # make the POST request add_course_url = urlparse.urljoin(course.ccx_connector, CCXCON_COURSEXS_URL) resp = oauth_ccxcon.post(url=add_course_url, json=payload, headers=headers, timeout=CCXCON_REQUEST_TIMEOUT) if resp.status_code >= 500: raise CCXConnServerError( 'Server returned error Status: %s, Content: %s', resp.status_code, resp.content) if resp.status_code >= 400: log.error("Error creating course on ccxcon. Status: %s, Content: %s", resp.status_code, resp.content) # this API performs a POST request both for POST and PATCH, but the POST returns 201 and the PATCH returns 200 elif resp.status_code != HTTP_200_OK and resp.status_code != HTTP_201_CREATED: log.error('Server returned unexpected status code %s', resp.status_code) else: log.debug('Request successful. Status: %s, Content: %s', resp.status_code, resp.content)
def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch', forced_grade=None): """ It will add the add-cert request into the xqueue. A new record will be created to track the certificate generation task. If an error occurs while adding the certificate to the queue, the task will have status 'error'. It also emits `edx.certificate.created` event for analytics. This method has not yet been updated (it predates the certificates revamp). If modifying this method, see also generate_user_certificates() in generation.py (which is very similar but is called from a celery task). In the future these methods will be unified. Args: student (User) course_key (CourseKey) Keyword Arguments: course (Course): Optionally provide the course object; if not provided it will be loaded. insecure - (Boolean) generation_mode - who has requested certificate generation. Its value should `batch` in case of django command and `self` if student initiated the request. forced_grade - a string indicating to replace grade parameter. if present grading will be skipped. """ if is_using_certificate_allowlist_and_is_on_allowlist(student, course_key): # Note that this will launch an asynchronous task, and so cannot return the certificate status. This is a # change from the older certificate code that tries to immediately create a cert. log.info( f'{course_key} is using allowlist certificates, and the user {student.id} is on its allowlist. ' f'Attempt will be made to regenerate an allowlist certificate.') return generate_allowlist_certificate_task(student, course_key) if not course: course = modulestore().get_course(course_key, depth=0) beta_testers_queryset = list_with_level(course, 'beta') if beta_testers_queryset.filter(username=student.username): message = 'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.' log.info(message.format(student.username, course_key)) return xqueue = XQueueCertInterface() if insecure: xqueue.use_https = False generate_pdf = not has_html_certificates_enabled(course) cert = xqueue.add_cert(student, course_key, course=course, generate_pdf=generate_pdf, forced_grade=forced_grade) message = 'Queued Certificate Generation task for {user} : {course}' log.info(message.format(user=student.id, course=course_key)) # If cert_status is not present in certificate valid_statuses (for example unverified) then # add_cert returns None and raises AttributeError while accessing cert attributes. if cert is None: return if CertificateStatuses.is_passing_status(cert.status): emit_certificate_event( 'created', student, course_key, course, { 'user_id': student.id, 'course_id': str(course_key), 'certificate_id': cert.verify_uuid, 'enrollment_mode': cert.mode, 'generation_mode': generation_mode }) return cert.status
def test_list_instructors(self): instructors = list_with_level(self.course, 'instructor') instructors_alternative = list_with_level_from_course_key( self.course.id, 'instructor') assert set(instructors) == set(self.instructors) assert set(instructors_alternative) == set(self.instructors)
def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_email=True): """ Add staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseBlockWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on access grant. """ list_staff = list_with_level(master_course.id, 'staff') list_instructor = list_with_level(master_course.id, 'instructor') with ccx_course(ccx_key) as course_ccx: email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) list_staff_ccx = list_with_level(course_ccx.id, 'staff') list_instructor_ccx = list_with_level(course_ccx.id, 'instructor') for staff in list_staff: # this call should be idempotent if staff not in list_staff_ccx: try: # Enroll the staff in the ccx enroll_email( course_id=ccx_key, student_email=staff.email, auto_enroll=True, email_students=send_email, email_params=email_params, ) # allow 'staff' access on ccx to staff of master course allow_access(course_ccx, staff, 'staff') except CourseEnrollmentException: log.warning( "Unable to enroll staff %s to course with id %s", staff.email, ccx_key) continue except SMTPException: continue for instructor in list_instructor: # this call should be idempotent if instructor not in list_instructor_ccx: try: # Enroll the instructor in the ccx enroll_email( course_id=ccx_key, student_email=instructor.email, auto_enroll=True, email_students=send_email, email_params=email_params, ) # allow 'instructor' access on ccx to instructor of master course allow_access(course_ccx, instructor, 'instructor') except CourseEnrollmentException: log.warning( "Unable to enroll instructor %s to course with id %s", instructor.email, ccx_key) continue except SMTPException: continue
def test_list_beta(self): beta_testers = list_with_level(self.course, 'beta') beta_testers_alternative = list_with_level_from_course_key( self.course.id, 'beta') assert set(beta_testers) == set(self.beta_testers) assert set(beta_testers_alternative) == set(self.beta_testers)
def _is_beta_tester(user, course): """ Check if the user is a beta tester in this course run """ beta_testers_queryset = list_with_level(course, 'beta') return beta_testers_queryset.filter(username=user.username).exists()
def test_list_instructors(self): instructors = list_with_level(self.course, 'instructor') assert set(instructors) == set(self.instructors)
def test_list_instructors(self): instructors = list_with_level(self.course, 'instructor') self.assertEqual(set(instructors), set(self.instructors))
def test_list_beta(self): beta_testers = list_with_level(self.course, 'beta') self.assertEqual(set(beta_testers), set(self.beta_testers))
def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch', forced_grade=None): """ It will add the add-cert request into the xqueue. A new record will be created to track the certificate generation task. If an error occurs while adding the certificate to the queue, the task will have status 'error'. It also emits `edx.certificate.created` event for analytics. Args: student (User) course_key (CourseKey) Keyword Arguments: course (Course): Optionally provide the course object; if not provided it will be loaded. insecure - (Boolean) generation_mode - who has requested certificate generation. Its value should `batch` in case of django command and `self` if student initiated the request. forced_grade - a string indicating to replace grade parameter. if present grading will be skipped. """ if not course: course = modulestore().get_course(course_key, depth=0) beta_testers_queryset = list_with_level(course, u'beta') if beta_testers_queryset.filter(username=student.username): message = u'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.' log.info(message.format(student.username, course_key)) return xqueue = XQueueCertInterface() if insecure: xqueue.use_https = False generate_pdf = not has_html_certificates_enabled(course) cert = xqueue.add_cert(student, course_key, course=course, generate_pdf=generate_pdf, forced_grade=forced_grade) message = u'Queued Certificate Generation task for {user} : {course}' log.info(message.format(user=student.id, course=course_key)) # If cert_status is not present in certificate valid_statuses (for example unverified) then # add_cert returns None and raises AttributeError while accessing cert attributes. if cert is None: return if CertificateStatuses.is_passing_status(cert.status): emit_certificate_event( 'created', student, course_key, course, { 'user_id': student.id, 'course_id': six.text_type(course_key), 'certificate_id': cert.verify_uuid, 'enrollment_mode': cert.mode, 'generation_mode': generation_mode }) return cert.status
def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_email=True): """ Add staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseDescriptorWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on access grant. """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) list_staff_ccx = list_with_level(course_ccx, 'staff') list_instructor_ccx = list_with_level(course_ccx, 'instructor') for staff in list_staff: # this call should be idempotent if staff not in list_staff_ccx: try: # Enroll the staff in the ccx enroll_email( course_id=ccx_key, student_email=staff.email, auto_enroll=True, email_students=send_email, email_params=email_params, ) # allow 'staff' access on ccx to staff of master course allow_access(course_ccx, staff, 'staff') except CourseEnrollmentException: log.warning( "Unable to enroll staff %s to course with id %s", staff.email, ccx_key ) continue except SMTPException: continue for instructor in list_instructor: # this call should be idempotent if instructor not in list_instructor_ccx: try: # Enroll the instructor in the ccx enroll_email( course_id=ccx_key, student_email=instructor.email, auto_enroll=True, email_students=send_email, email_params=email_params, ) # allow 'instructor' access on ccx to instructor of master course allow_access(course_ccx, instructor, 'instructor') except CourseEnrollmentException: log.warning( "Unable to enroll instructor %s to course with id %s", instructor.email, ccx_key ) continue except SMTPException: continue