Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
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,
                )
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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,
                )
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
    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)
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
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)
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
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
Ejemplo n.º 23
0
 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)
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
 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)
Ejemplo n.º 26
0
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))
Ejemplo n.º 30
0
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
Ejemplo n.º 31
0
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