Esempio n. 1
0
    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
Esempio n. 2
0
    def setUp(self):
        super().setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX',
                                           number='DemoX',
                                           display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(
            course_key=self.course.id, enabled=True)
        self.user.profile.name = 'Foo Bar'
        self.user.profile.save()
Esempio n. 3
0
    def setUp(self):
        super(CreditServiceTests, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX',
                                           number='DemoX',
                                           display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(
            course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id,
                                                  name='Foo Bar')
Esempio n. 4
0
def run():
    """
    Executed during django startup
    """
    django_utils_translation.patch()

    autostartup()

    add_mimetypes()

    if settings.FEATURES.get('USE_CUSTOM_THEME', False):
        enable_theme()

    if settings.FEATURES.get('USE_MICROSITES', False):
        enable_microsites()

    if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
        enable_third_party_auth()

    # Initialize Segment.io analytics module. Flushes first time a message is received and
    # every 50 messages thereafter, or if 10 seconds have passed since last flush
    if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(
            settings, 'SEGMENT_IO_LMS_KEY'):
        analytics.init(settings.SEGMENT_IO_LMS_KEY, flush_at=50)

    # register any dependency injections that we need to support in edx_proctoring
    # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit
    # as well as the instructor dashboard (for deleting student attempts)
    if settings.FEATURES.get('ENABLE_PROCTORED_EXAMS'):
        set_runtime_service('credit', CreditService())
        set_runtime_service('instructor', InstructorService())
Esempio n. 5
0
def run():
    """
    Executed during django startup
    """
    django_utils_translation.patch()

    autostartup()

    add_mimetypes()

    if settings.FEATURES.get('USE_CUSTOM_THEME', False):
        enable_stanford_theme()

    if settings.FEATURES.get('USE_MICROSITES', False):
        enable_microsites()

    if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
        enable_third_party_auth()

    # Initialize Segment analytics module by setting the write_key.
    if settings.LMS_SEGMENT_KEY:
        analytics.write_key = settings.LMS_SEGMENT_KEY

    # register any dependency injections that we need to support in edx_proctoring
    # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit
    # as well as the instructor dashboard (for deleting student attempts)
    if settings.FEATURES.get('ENABLE_PROCTORED_EXAMS'):
        # Import these here to avoid circular dependencies of the form:
        # edx-platform app --> DRF --> django translation --> edx-platform app
        from edx_proctoring.runtime import set_runtime_service
        from instructor.services import InstructorService
        from openedx.core.djangoapps.credit.services import CreditService
        set_runtime_service('credit', CreditService())
        set_runtime_service('instructor', InstructorService())
Esempio n. 6
0
    def setUp(self):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
Esempio n. 7
0
    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org="edX", number="DemoX", display_name="Demo_Course")
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name="Foo Bar")
Esempio n. 8
0
def run():
    """
    Executed during django startup
    """
    django_db_models_options.patch()

    # To override the settings before executing the autostartup() for python-social-auth
    if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
        enable_third_party_auth()

    # Comprehensive theming needs to be set up before django startup,
    # because modifying django template paths after startup has no effect.
    if is_comprehensive_theming_enabled():
        enable_theming()

    # We currently use 2 template rendering engines, mako and django_templates,
    # and one of them (django templates), requires the directories be added
    # before the django.setup().
    microsite.enable_microsites_pre_startup(log)

    django.setup()

    autostartup()

    add_mimetypes()

    # Mako requires the directories to be added after the django setup.
    microsite.enable_microsites(log)

    # Initialize Segment analytics module by setting the write_key.
    if settings.LMS_SEGMENT_KEY:
        analytics.write_key = settings.LMS_SEGMENT_KEY

    # register any dependency injections that we need to support in edx_proctoring
    # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit
    if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
        # Import these here to avoid circular dependencies of the form:
        # edx-platform app --> DRF --> django translation --> edx-platform app
        from edx_proctoring.runtime import set_runtime_service
        from lms.djangoapps.instructor.services import InstructorService
        from openedx.core.djangoapps.credit.services import CreditService
        set_runtime_service('credit', CreditService())

        # register InstructorService (for deleting student attempts and user staff access roles)
        set_runtime_service('instructor', InstructorService())

    # In order to allow modules to use a handler url, we need to
    # monkey-patch the x_module library.
    # TODO: Remove this code when Runtimes are no longer created by modulestores
    # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes
    xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url
    xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url

    # Set the version of docs that help-tokens will go to.
    settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE
    settings.HELP_TOKENS_VERSION = doc_version()

    # validate configurations on startup
    validate_lms_config(settings)
Esempio n. 9
0
def run():
    """
    Executed during django startup

    NOTE: DO **NOT** add additional code to this method or this file! The Platform Team
          is moving all startup code to more standard locations using Django best practices.
    """
    django_db_models_options.patch()

    # We currently use 2 template rendering engines, mako and django_templates,
    # and one of them (django templates), requires the directories be added
    # before the django.setup().
    microsite.enable_microsites_pre_startup(log)

    django.setup()

    autostartup()

    add_mimetypes()

    # Mako requires the directories to be added after the django setup.
    microsite.enable_microsites(log)

    # register any dependency injections that we need to support in edx_proctoring
    # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit and
    # lms.djangoapps.grades
    if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
        # Import these here to avoid circular dependencies of the form:
        # edx-platform app --> DRF --> django translation --> edx-platform app
        from edx_proctoring.runtime import set_runtime_service
        from lms.djangoapps.instructor.services import InstructorService
        from openedx.core.djangoapps.credit.services import CreditService
        from lms.djangoapps.grades.services import GradesService
        set_runtime_service('credit', CreditService())

        # register InstructorService (for deleting student attempts and user staff access roles)
        set_runtime_service('instructor', InstructorService())

        set_runtime_service('grades', GradesService())

    # In order to allow modules to use a handler url, we need to
    # monkey-patch the x_module library.
    # TODO: Remove this code when Runtimes are no longer created by modulestores
    # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes
    xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url
    xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url

    # Set the version of docs that help-tokens will go to.
    settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE
    settings.HELP_TOKENS_VERSION = doc_version()

    # validate configurations on startup
    validate_lms_config(settings)
Esempio n. 10
0
def run():
    """
    Executed during django startup
    """
    third_party_auth.patch()

    # To override the settings before executing the autostartup() for python-social-auth
    if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
        enable_third_party_auth()

    if settings.FEATURES.get('USE_MICROSITES', False):
        enable_microsites_pre_startup()

    django.setup()

    autostartup()

    add_mimetypes()

    if settings.FEATURES.get('USE_CUSTOM_THEME', False):
        enable_stanford_theme()

    if settings.FEATURES.get('USE_MICROSITES', False):
        enable_microsites()

    # Initialize Segment analytics module by setting the write_key.
    if settings.LMS_SEGMENT_KEY:
        analytics.write_key = settings.LMS_SEGMENT_KEY

    # register any dependency injections that we need to support in edx_proctoring
    # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit
    if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
        # Import these here to avoid circular dependencies of the form:
        # edx-platform app --> DRF --> django translation --> edx-platform app
        from edx_proctoring.runtime import set_runtime_service
        from instructor.services import InstructorService
        from openedx.core.djangoapps.credit.services import CreditService
        set_runtime_service('credit', CreditService())

        # register InstructorService (for deleting student attempts and user staff access roles)
        set_runtime_service('instructor', InstructorService())

    # In order to allow modules to use a handler url, we need to
    # monkey-patch the x_module library.
    # TODO: Remove this code when Runtimes are no longer created by modulestores
    # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes
    xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url
    xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url
Esempio n. 11
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """

    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org="edX", number="DemoX", display_name="Demo_Course")
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name="Foo Bar")

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        self.assertIsNone(self.service.get_credit_state(0, self.course.id))

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        enrollment.is_active = False
        enrollment.save()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        self.credit_course.enabled = False
        self.credit_course.save()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}],
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id, self.course.id, "grade", "grade")

        credit_state = self.service.get_credit_state(self.user.id, self.course.id)

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state["enrollment_mode"], "honor")
        self.assertEqual(credit_state["profile_fullname"], "Foo Bar")
        self.assertEqual(len(credit_state["credit_requirement_status"]), 1)
        self.assertEqual(credit_state["credit_requirement_status"][0]["name"], "grade")
        self.assertEqual(credit_state["credit_requirement_status"][0]["status"], "satisfied")

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}],
        )

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(0, self.course.id, "grade", "grade")
        self.assertIsNone(retval)

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}],
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id, unicode(self.course.id), "grade", "grade")

        credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id))

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state["enrollment_mode"], "honor")
        self.assertEqual(credit_state["profile_fullname"], "Foo Bar")
        self.assertEqual(len(credit_state["credit_requirement_status"]), 1)
        self.assertEqual(credit_state["credit_requirement_status"][0]["name"], "grade")
        self.assertEqual(credit_state["credit_requirement_status"][0]["status"], "satisfied")
Esempio n. 12
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """
    def setUp(self):
        super().setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX',
                                           number='DemoX',
                                           display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(
            course_key=self.course.id, enabled=True)
        self.user.profile.name = 'Foo Bar'
        self.user.profile.save()

    def enroll(self, course_id=None, mode=CourseMode.VERIFIED):
        """
        Enroll the test user in the given course's mode. Use course/mode if they are
        provided.
        """
        if course_id is None:
            course_id = self.course.id
        return CourseEnrollment.enroll(self.user, course_id, mode=mode)

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        assert self.service.get_credit_state(0, self.course.id) is None

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        assert self.service.get_credit_state(self.user.id,
                                             self.course.id) is None

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = self.enroll()
        enrollment.is_active = False
        enrollment.save()

        assert self.service.get_credit_state(self.user.id,
                                             self.course.id) is None

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        self.enroll()

        self.credit_course.enabled = False
        self.credit_course.save()

        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        assert credit_state is not None
        assert not credit_state['is_credit_course']

    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        assert self.service.get_credit_state(self.user.id,
                                             self.course.id) is None

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        assert self.service.is_credit_course(self.course.id)

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)

        assert credit_state is not None
        assert credit_state['is_credit_course']
        assert credit_state['enrollment_mode'] == 'verified'
        assert credit_state['profile_fullname'] == 'Foo Bar'
        assert len(credit_state['credit_requirement_status']) == 1
        assert credit_state['credit_requirement_status'][0]['name'] == 'grade'
        assert credit_state['credit_requirement_status'][0][
            'status'] == 'satisfied'

    def test_remove_credit_requirement_status(self):
        """
        Happy path when deleting the requirement status.
        """
        assert self.service.is_credit_course(self.course.id)

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')

        # now the status should be "satisfied" when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        assert credit_state['credit_requirement_status'][0][
            'status'] == 'satisfied'

        # remove the requirement status.
        self.service.remove_credit_requirement_status(self.user.id,
                                                      self.course.id, 'grade',
                                                      'grade')

        # now the status should be None when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        assert credit_state['credit_requirement_status'][0]['status'] is None

    def test_invalid_user(self):
        """
        Try removing requirement status with a invalid user_id
        """

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(  # lint-amnesty, pylint: disable=assignment-from-none
            self.user.id, self.course.id, 'grade', 'grade')
        assert retval is None

        # remove the requirement status with the invalid user id
        retval = self.service.remove_credit_requirement_status(  # lint-amnesty, pylint: disable=assignment-from-none
            0, self.course.id, 'grade', 'grade')
        assert retval is None

    def test_remove_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit',
                                                number='NoCredit',
                                                display_name='Demo_Course')

        assert not self.service.is_credit_course(no_credit_course.id)

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.remove_credit_requirement_status(self.user.id,
                                                      no_credit_course.id,
                                                      'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     no_credit_course.id)

        assert credit_state is not None
        assert not credit_state['is_credit_course']
        assert len(credit_state['credit_requirement_status']) == 0

    def test_course_name(self):
        """
        Make sure we can get back the optional course name
        """

        self.enroll()

        # make sure it is not returned by default
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        assert 'course_name' not in credit_state

        # now make sure it is in there when we pass in the flag
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id,
                                                     return_course_info=True)
        assert 'course_name' in credit_state
        assert credit_state['course_name'] == self.course.display_name

    @patch("openedx.core.djangoapps.credit.services.log")
    def test_get_info_from_non_existent_course(self, exception_log):
        """
        Make sure we catch the CourseOverview.DoesNotExist exception and log it instead of raising
        """
        with patch(
                "openedx.core.djangoapps.content.course_overviews.models.CourseOverview.get_from_id",
                side_effect=CourseOverview.DoesNotExist):

            self.enroll()
            credit_state = self.service.get_credit_state(
                self.user.id, self.course.id, return_course_info=True)

            assert credit_state is not None
            # When exception is caught, the course_end_date should always be in past
            assert credit_state["course_end_date"] < datetime.now()

            exception_log.exception.assert_called_once_with(
                "Could not get name and end_date for course %s, This happened because we were unable to "
                "get/create CourseOverview object for the course. It's possible that the Course has been deleted.",
                str(self.course.id))

    def test_set_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit',
                                                number='NoCredit',
                                                display_name='Demo_Course')

        assert not self.service.is_credit_course(no_credit_course.id)

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.set_credit_requirement_status(self.user.id,
                                                   no_credit_course.id,
                                                   'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     no_credit_course.id)

        assert credit_state is not None
        assert not credit_state['is_credit_course']
        assert len(credit_state['credit_requirement_status']) == 0

    @ddt.data(CourseMode.AUDIT, CourseMode.HONOR, CourseMode.CREDIT_MODE)
    def test_set_status_non_verified_enrollment(self, mode):
        """
        Test that we can still try to update a credit status but return quickly if
        user has non-credit eligible enrollment.
        """
        self.enroll(mode=mode)

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # this should be a no-op
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')
        # Verify credit requirement status for user in the course should be None.
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        assert credit_state is not None
        assert credit_state['enrollment_mode'] == mode
        assert len(credit_state['credit_requirement_status']) == 1
        assert credit_state['credit_requirement_status'][0]['status'] is None
        assert credit_state['credit_requirement_status'][0][
            'status_date'] is None

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(  # lint-amnesty, pylint: disable=assignment-from-none
            0, self.course.id, 'grade', 'grade')
        assert retval is None

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   str(self.course.id),
                                                   'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     str(self.course.id))

        assert credit_state is not None
        assert credit_state['enrollment_mode'] == 'verified'
        assert credit_state['profile_fullname'] == 'Foo Bar'
        assert len(credit_state['credit_requirement_status']) == 1
        assert credit_state['credit_requirement_status'][0]['name'] == 'grade'
        assert credit_state['credit_requirement_status'][0][
            'status'] == 'satisfied'
Esempio n. 13
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """
    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX',
                                           number='DemoX',
                                           display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(
            course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id,
                                                  name='Foo Bar')

    def enroll(self, course_id=None):
        """
        Enroll the test user in the given course's honor mode, or the test
        course if not provided.
        """
        if course_id is None:
            course_id = self.course.id
        return CourseEnrollment.enroll(self.user, course_id, mode='honor')

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        self.assertIsNone(self.service.get_credit_state(0, self.course.id))

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = self.enroll()
        enrollment.is_active = False
        enrollment.save()

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        self.enroll()

        self.credit_course.enabled = False
        self.credit_course.save()

        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])

    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        self.assertTrue(self.service.is_credit_course(self.course.id))

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)

        self.assertIsNotNone(credit_state)
        self.assertTrue(credit_state['is_credit_course'])
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'],
                         'grade')
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'],
            'satisfied')

    def test_remove_credit_requirement_status(self):
        """
        Happy path when deleting the requirement status.
        """
        self.assertTrue(self.service.is_credit_course(self.course.id))

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')

        # now the status should be "satisfied" when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'],
            "satisfied")

        # remove the requirement status.
        self.service.remove_credit_requirement_status(self.user.id,
                                                      self.course.id, 'grade',
                                                      'grade')

        # now the status should be None when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'], None)

    def test_invalid_user(self):
        """
        Try removing requirement status with a invalid user_id
        """

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            self.user.id, self.course.id, 'grade', 'grade')
        self.assertIsNone(retval)

        # remove the requirement status with the invalid user id
        retval = self.service.remove_credit_requirement_status(
            0, self.course.id, 'grade', 'grade')
        self.assertIsNone(retval)

    def test_remove_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit',
                                                number='NoCredit',
                                                display_name='Demo_Course')

        self.assertFalse(self.service.is_credit_course(no_credit_course.id))

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.remove_credit_requirement_status(self.user.id,
                                                      no_credit_course.id,
                                                      'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     no_credit_course.id)

        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])
        self.assertEqual(len(credit_state['credit_requirement_status']), 0)

    def test_course_name(self):
        """
        Make sure we can get back the optional course name
        """

        self.enroll()

        # make sure it is not returned by default
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)
        self.assertNotIn('course_name', credit_state)

        # now make sure it is in there when we pass in the flag
        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id,
                                                     return_course_name=True)
        self.assertIn('course_name', credit_state)
        self.assertEqual(credit_state['course_name'], self.course.display_name)

    def test_set_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit',
                                                number='NoCredit',
                                                display_name='Demo_Course')

        self.assertFalse(self.service.is_credit_course(no_credit_course.id))

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.set_credit_requirement_status(self.user.id,
                                                   no_credit_course.id,
                                                   'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     no_credit_course.id)

        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])
        self.assertEqual(len(credit_state['credit_requirement_status']), 0)

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            0, self.course.id, 'grade', 'grade')
        self.assertIsNone(retval)

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        self.enroll()

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   unicode(self.course.id),
                                                   'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     unicode(self.course.id))

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'],
                         'grade')
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'],
            'satisfied')
Esempio n. 14
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """

    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        self.assertIsNone(self.service.get_credit_state(0, self.course.id))

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        enrollment.is_active = False
        enrollment.save()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        self.credit_course.enabled = False
        self.credit_course.save()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    @unittest.skipIf(settings.FEATURES.get('DISABLE_CME_REGISTRATION_TESTS', False), 'This is contrived and does not work for CME')
    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(
            self.user.id,
            self.course.id,
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, self.course.id)

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            0,
            self.course.id,
            'grade',
            'grade'
        )
        self.assertIsNone(retval)

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(
            self.user.id,
            unicode(self.course.id),
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id))

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')
Esempio n. 15
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """

    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')

    def enroll(self, course_id=None):
        """
        Enroll the test user in the given course's honor mode, or the test
        course if not provided.
        """
        if course_id is None:
            course_id = self.course.id
        return CourseEnrollment.enroll(self.user, course_id, mode='honor')

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        self.assertIsNone(self.service.get_credit_state(0, self.course.id))

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = self.enroll()
        enrollment.is_active = False
        enrollment.save()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        self.enroll()

        self.credit_course.enabled = False
        self.credit_course.save()

        credit_state = self.service.get_credit_state(self.user.id, self.course.id)
        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])

    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id))

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        self.assertTrue(self.service.is_credit_course(self.course.id))

        self.enroll()

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(
            self.user.id,
            self.course.id,
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, self.course.id)

        self.assertIsNotNone(credit_state)
        self.assertTrue(credit_state['is_credit_course'])
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')

    def test_remove_credit_requirement_status(self):
        """
        Happy path when deleting the requirement status.
        """
        self.assertTrue(self.service.is_credit_course(self.course.id))

        self.enroll()

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(
            self.user.id,
            self.course.id,
            'grade',
            'grade'
        )

        # now the status should be "satisfied" when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id, self.course.id)
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], "satisfied")

        # remove the requirement status.
        self.service.remove_credit_requirement_status(
            self.user.id,
            self.course.id,
            'grade',
            'grade'
        )

        # now the status should be None when looking at the credit_requirement_status list
        credit_state = self.service.get_credit_state(self.user.id, self.course.id)
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], None)

    def test_invalid_user(self):
        """
        Try removing requirement status with a invalid user_id
        """

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            self.user.id,
            self.course.id,
            'grade',
            'grade'
        )
        self.assertIsNone(retval)

        # remove the requirement status with the invalid user id
        retval = self.service.remove_credit_requirement_status(
            0,
            self.course.id,
            'grade',
            'grade'
        )
        self.assertIsNone(retval)

    def test_remove_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course')

        self.assertFalse(self.service.is_credit_course(no_credit_course.id))

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.remove_credit_requirement_status(
            self.user.id,
            no_credit_course.id,
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id)

        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])
        self.assertEqual(len(credit_state['credit_requirement_status']), 0)

    def test_course_name(self):
        """
        Make sure we can get back the optional course name
        """

        self.enroll()

        # make sure it is not returned by default
        credit_state = self.service.get_credit_state(self.user.id, self.course.id)
        self.assertNotIn('course_name', credit_state)

        # now make sure it is in there when we pass in the flag
        credit_state = self.service.get_credit_state(self.user.id, self.course.id, return_course_name=True)
        self.assertIn('course_name', credit_state)
        self.assertEqual(credit_state['course_name'], self.course.display_name)

    def test_set_status_non_credit(self):
        """
        assert that we can still try to update a credit status but return quickly if
        a course is not credit eligible
        """

        no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course')

        self.assertFalse(self.service.is_credit_course(no_credit_course.id))

        self.enroll(no_credit_course.id)

        # this should be a no-op
        self.service.set_credit_requirement_status(
            self.user.id,
            no_credit_course.id,
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id)

        self.assertIsNotNone(credit_state)
        self.assertFalse(credit_state['is_credit_course'])
        self.assertEqual(len(credit_state['credit_requirement_status']), 0)

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            0,
            self.course.id,
            'grade',
            'grade'
        )
        self.assertIsNone(retval)

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        self.enroll()

        # set course requirements
        set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Grade",
                    "criteria": {
                        "min_grade": 0.8
                    },
                },
            ]
        )

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(
            self.user.id,
            unicode(self.course.id),
            'grade',
            'grade'
        )

        credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id))

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
        self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')
Esempio n. 16
0
class CreditServiceTests(ModuleStoreTestCase):
    """
    Tests for the Credit xBlock service
    """
    def setUp(self, **kwargs):
        super(CreditServiceTests, self).setUp()

        self.service = CreditService()
        self.course = CourseFactory.create(org='edX',
                                           number='DemoX',
                                           display_name='Demo_Course')
        self.credit_course = CreditCourse.objects.create(
            course_key=self.course.id, enabled=True)
        self.profile = UserProfile.objects.create(user_id=self.user.id,
                                                  name='Foo Bar')

    def test_user_not_found(self):
        """
        Makes sure that get_credit_state returns None if user_id cannot be found
        """

        self.assertIsNone(self.service.get_credit_state(0, self.course.id))

    def test_user_not_enrolled(self):
        """
        Makes sure that get_credit_state returns None if user_id is not enrolled
        in the test course
        """

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_inactive_enrollment(self):
        """
        Makes sure that get_credit_state returns None if the user's enrollment is
        inactive
        """

        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        enrollment.is_active = False
        enrollment.save()

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_not_credit_course(self):
        """
        Makes sure that get_credit_state returns None if the test course is not
        Credit eligible
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        self.credit_course.enabled = False
        self.credit_course.save()

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_no_profile_name(self):
        """
        Makes sure that get_credit_state returns None if the user does not
        have a corresponding UserProfile. This shouldn't happen in
        real environments
        """

        profile = UserProfile.objects.get(user_id=self.user.id)
        profile.delete()

        self.assertIsNone(
            self.service.get_credit_state(self.user.id, self.course.id))

    def test_get_and_set_credit_state(self):
        """
        Happy path through the service
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   self.course.id, 'grade',
                                                   'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     self.course.id)

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'],
                         'grade')
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'],
            'satisfied')

    def test_bad_user(self):
        """
        Try setting requirements status with a bad user_id
        """

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        retval = self.service.set_credit_requirement_status(
            0, self.course.id, 'grade', 'grade')
        self.assertIsNone(retval)

    def test_course_id_string(self):
        """
        Make sure we can pass a course_id (string) and get back correct results as well
        """

        CourseEnrollment.enroll(self.user, self.course.id)

        # set course requirements
        set_credit_requirements(self.course.id, [
            {
                "namespace": "grade",
                "name": "grade",
                "display_name": "Grade",
                "criteria": {
                    "min_grade": 0.8
                },
            },
        ])

        # mark the grade as satisfied
        self.service.set_credit_requirement_status(self.user.id,
                                                   unicode(self.course.id),
                                                   'grade', 'grade')

        credit_state = self.service.get_credit_state(self.user.id,
                                                     unicode(self.course.id))

        self.assertIsNotNone(credit_state)
        self.assertEqual(credit_state['enrollment_mode'], 'honor')
        self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
        self.assertEqual(len(credit_state['credit_requirement_status']), 1)
        self.assertEqual(credit_state['credit_requirement_status'][0]['name'],
                         'grade')
        self.assertEqual(
            credit_state['credit_requirement_status'][0]['status'],
            'satisfied')
Esempio n. 17
0
def get_module_system_for_user(
        user,
        student_data,  # TODO
        # Arguments preceding this comment have user binding, those following don't
        descriptor,
        course_id,
        track_function,
        xqueue_callback_url_prefix,
        request_token,
        position=None,
        wrap_xmodule_display=True,
        grade_bucket_type=None,
        static_asset_path='',
        user_location=None,
        disable_staff_debug_info=False,
        course=None
):
    """
    Helper function that returns a module system and student_data bound to a user and a descriptor.

    The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module,
    to allow an existing module to be re-bound to a user.  Most of the user bindings happen when creating the
    closures that feed the instantiation of ModuleSystem.

    The arguments fall into two categories: those that have explicit or implicit user binding, which are user
    and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which
    are all the other arguments.  Ultimately, this isn't too different than how get_module_for_descriptor_internal
    was before refactoring.

    Arguments:
        see arguments for get_module()
        request_token (str): A token unique to the request use by xblock initialization

    Returns:
        (LmsModuleSystem, KvsFieldData):  (module system, student_data) bound to, primarily, the user and descriptor
    """

    def make_xqueue_callback(dispatch='score_update'):
        """
        Returns fully qualified callback URL for external queueing system
        """
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(
                course_id=text_type(course_id),
                userid=str(user.id),
                mod_id=text_type(descriptor.location),
                dispatch=dispatch
            ),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': XQUEUE_INTERFACE,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user=user,
            descriptor=descriptor,
            student_data=student_data,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

    def get_event_handler(event_type):
        """
        Return an appropriate function to handle the event.

        Returns None if no special processing is required.
        """
        handlers = {
            'grade': handle_grade_event,
        }
        if completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING):
            handlers.update({
                'completion': handle_completion_event,
                'progress': handle_deprecated_progress_event,
            })
        return handlers.get(event_type)

    def publish(block, event_type, event):
        """
        A function that allows XModules to publish events.
        """
        handle_event = get_event_handler(event_type)
        if handle_event and not is_masquerading_as_specific_student(user, course_id):
            handle_event(block, event)
        else:
            context = contexts.course_context_from_course_id(course_id)
            if block.runtime.user_id:
                context['user_id'] = block.runtime.user_id
            context['asides'] = {}
            for aside in block.runtime.get_asides(block):
                if hasattr(aside, 'get_event_context'):
                    aside_event_info = aside.get_event_context(event_type, event)
                    if aside_event_info is not None:
                        context['asides'][aside.scope_ids.block_type] = aside_event_info
            with tracker.get_tracker().context(event_type, context):
                track_function(event_type, event)

    def handle_completion_event(block, event):
        """
        Submit a completion object for the block.
        """
        if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING):
            raise Http404
        else:
            BlockCompletion.objects.submit_completion(
                user=user,
                course_key=course_id,
                block_key=block.scope_ids.usage_id,
                completion=event['completion'],
            )

    def handle_grade_event(block, event):
        """
        Submit a grade for the block.
        """
        SCORE_PUBLISHED.send(
            sender=None,
            block=block,
            user=user,
            raw_earned=event['value'],
            raw_possible=event['max_value'],
            only_if_higher=event.get('only_if_higher'),
            score_deleted=event.get('score_deleted'),
            grader_response=event.get('grader_response')
        )

    def handle_deprecated_progress_event(block, event):
        """
        DEPRECATED: Submit a completion for the block represented by the
        progress event.

        This exists to support the legacy progress extension used by
        edx-solutions.  New XBlocks should not emit these events, but instead
        emit completion events directly.
        """
        if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING):
            raise Http404
        else:
            requested_user_id = event.get('user_id', user.id)
            if requested_user_id != user.id:
                log.warning("{} tried to submit a completion on behalf of {}".format(user, requested_user_id))
                return

            # If blocks explicitly declare support for the new completion API,
            # we expect them to emit 'completion' events,
            # and we ignore the deprecated 'progress' events
            # in order to avoid duplicate work and possibly conflicting semantics.
            if not getattr(block, 'has_custom_completion', False):
                BlockCompletion.objects.submit_completion(
                    user=user,
                    course_key=course_id,
                    block_key=block.scope_ids.usage_id,
                    completion=1.0,
                )

    def rebind_noauth_module_to_user(module, real_user):
        """
        A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser.

        Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler.

        Arguments:
            module (any xblock type):  the module to rebind
            real_user (django.contrib.auth.models.User):  the user to bind to

        Returns:
            nothing (but the side effect is that module is re-bound to real_user)
        """
        if user.is_authenticated:
            err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to "
                       "an anonymous user")
            log.error(err_msg)
            raise LmsModuleRenderError(err_msg)

        field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents(
            course_id,
            real_user,
            module.descriptor,
            asides=XBlockAsidesConfig.possible_asides(),
        )
        student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user))

        (inner_system, inner_student_data) = get_module_system_for_user(
            user=real_user,
            student_data=student_data_real_user,  # These have implicit user bindings, rest of args considered not to
            descriptor=module.descriptor,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

        module.descriptor.bind_for_student(
            inner_system,
            real_user.id,
            [
                partial(OverrideFieldData.wrap, real_user, course),
                partial(LmsFieldData, student_data=inner_student_data),
            ],
        )

        module.descriptor.scope_ids = (
            module.descriptor.scope_ids._replace(user_id=real_user.id)
        )
        module.scope_ids = module.descriptor.scope_ids  # this is needed b/c NamedTuples are immutable
        # now bind the module to the new ModuleSystem instance and vice-versa
        module.runtime = inner_system
        inner_system.xmodule_instance = module

    # Build a list of wrapping functions that will be applied in order
    # to the Fragment content coming out of the xblocks that are about to be rendered.
    block_wrappers = []

    if is_masquerading_as_specific_student(user, course_id):
        block_wrappers.append(filter_displayed_blocks)

    if settings.FEATURES.get("LICENSING", False):
        block_wrappers.append(wrap_with_license)

    # Wrap the output display in a single div to allow for the XModule
    # javascript to be bound correctly
    if wrap_xmodule_display is True:
        block_wrappers.append(partial(
            wrap_xblock,
            'LmsRuntime',
            extra_data={'course-id': text_type(course_id)},
            usage_id_serializer=lambda usage_id: quote_slashes(text_type(usage_id)),
            request_token=request_token,
        ))

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    # Rewrite urls beginning in /static to point to course-specific content
    block_wrappers.append(partial(
        replace_static_urls,
        getattr(descriptor, 'data_dir', None),
        course_id=course_id,
        static_asset_path=static_asset_path or descriptor.static_asset_path
    ))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    block_wrappers.append(partial(replace_course_urls, course_id))

    # this will rewrite intra-courseware links (/jump_to_id/<id>). This format
    # is an improvement over the /course/... format for studio authored courses,
    # because it is agnostic to course-hierarchy.
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work.
    block_wrappers.append(partial(
        replace_jump_to_id_urls,
        course_id,
        reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''}),
    ))

    if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
        if is_masquerading_as_specific_student(user, course_id):
            # When masquerading as a specific student, we want to show the debug button
            # unconditionally to enable resetting the state of the student we are masquerading as.
            # We already know the user has staff access when masquerading is active.
            staff_access = True
            # To figure out whether the user has instructor access, we temporarily remove the
            # masquerade_settings from the real_user.  With the masquerading settings in place,
            # the result would always be "False".
            masquerade_settings = user.real_user.masquerade_settings
            del user.real_user.masquerade_settings
            user.real_user.masquerade_settings = masquerade_settings
        else:
            staff_access = has_access(user, 'staff', descriptor, course_id)
        if staff_access:
            block_wrappers.append(partial(add_staff_markup, user, disable_staff_debug_info))

    # These modules store data using the anonymous_student_id as a key.
    # To prevent loss of data, we will continue to provide old modules with
    # the per-student anonymized id (as we have in the past),
    # while giving selected modules a per-course anonymized id.
    # As we have the time to manually test more modules, we can add to the list
    # of modules that get the per-course anonymized id.
    is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor)
    module_class = getattr(descriptor, 'module_class', None)
    is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule)
    if is_pure_xblock or is_lti_module:
        anonymous_student_id = anonymous_id_for_user(user, course_id)
    else:
        anonymous_student_id = anonymous_id_for_user(user, None)

    field_data = LmsFieldData(descriptor._field_data, student_data)  # pylint: disable=protected-access

    user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id))

    system = LmsModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.runtime.resources_fs,
        get_module=inner_get_module,
        user=user,
        debug=settings.DEBUG,
        hostname=settings.SITE_NAME,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(
            static_replace.replace_course_urls,
            course_key=course_id
        ),
        replace_jump_to_id_urls=partial(
            static_replace.replace_jump_to_id_urls,
            course_id=course_id,
            jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''})
        ),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=anonymous_student_id,
        course_id=course_id,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.runtime.mixologist._mixins,  # pylint: disable=protected-access
        wrappers=block_wrappers,
        get_real_user=user_by_anonymous_id,
        services={
            'fs': FSService(),
            'field-data': field_data,
            'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
            'verification': XBlockVerificationService(),
            'proctoring': ProctoringService(),
            'milestones': milestones_helpers.get_service(),
            'credit': CreditService(),
            'bookmarks': BookmarksService(user=user),
            'gating': GatingService(),
        },
        get_user_role=lambda: get_user_role(user, course_id),
        descriptor_runtime=descriptor._runtime,  # pylint: disable=protected-access
        rebind_noauth_module_to_user=rebind_noauth_module_to_user,
        user_location=user_location,
        request_token=request_token,
    )

    # pass position specified in URL to module through ModuleSystem
    if position is not None:
        try:
            position = int(position)
        except (ValueError, TypeError):
            log.exception('Non-integer %r passed as position.', position)
            position = None

    system.set('position', position)

    system.set(u'user_is_staff', user_is_staff)
    system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global')))
    system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
    system.set(u'days_early_for_beta', descriptor.days_early_for_beta)

    # make an ErrorDescriptor -- assuming that the descriptor's system is ok
    if has_access(user, u'staff', descriptor.location, course_id):
        system.error_descriptor_class = ErrorDescriptor
    else:
        system.error_descriptor_class = NonStaffErrorDescriptor

    return system, field_data
Esempio n. 18
0
def get_module_system_for_user(user, student_data,  # TODO  # pylint: disable=too-many-statements
                               # Arguments preceding this comment have user binding, those following don't
                               descriptor, course_id, track_function, xqueue_callback_url_prefix,
                               request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None,
                               static_asset_path='', user_location=None, disable_staff_debug_info=False,
                               course=None):
    """
    Helper function that returns a module system and student_data bound to a user and a descriptor.

    The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module,
    to allow an existing module to be re-bound to a user.  Most of the user bindings happen when creating the
    closures that feed the instantiation of ModuleSystem.

    The arguments fall into two categories: those that have explicit or implicit user binding, which are user
    and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which
    are all the other arguments.  Ultimately, this isn't too different than how get_module_for_descriptor_internal
    was before refactoring.

    Arguments:
        see arguments for get_module()
        request_token (str): A token unique to the request use by xblock initialization

    Returns:
        (LmsModuleSystem, KvsFieldData):  (module system, student_data) bound to, primarily, the user and descriptor
    """

    def make_xqueue_callback(dispatch='score_update'):
        """
        Returns fully qualified callback URL for external queueing system
        """
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(
                course_id=course_id.to_deprecated_string(),
                userid=str(user.id),
                mod_id=descriptor.location.to_deprecated_string(),
                dispatch=dispatch
            ),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': XQUEUE_INTERFACE,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user=user,
            descriptor=descriptor,
            student_data=student_data,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

    def _fulfill_content_milestones(user, course_key, content_key):
        """
        Internal helper to handle milestone fulfillments for the specified content module
        """
        # Fulfillment Use Case: Entrance Exam
        # If this module is part of an entrance exam, we'll need to see if the student
        # has reached the point at which they can collect the associated milestone
        if milestones_helpers.is_entrance_exams_enabled():
            course = modulestore().get_course(course_key)
            content = modulestore().get_item(content_key)
            entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False)
            in_entrance_exam = getattr(content, 'in_entrance_exam', False)
            if entrance_exam_enabled and in_entrance_exam:
                # We don't have access to the true request object in this context, but we can use a mock
                request = RequestFactory().request()
                request.user = user
                exam_pct = get_entrance_exam_score(request, course)
                if exam_pct >= course.entrance_exam_minimum_score_pct:
                    exam_key = UsageKey.from_string(course.entrance_exam_id)
                    relationship_types = milestones_helpers.get_milestone_relationship_types()
                    content_milestones = milestones_helpers.get_course_content_milestones(
                        course_key,
                        exam_key,
                        relationship=relationship_types['FULFILLS']
                    )
                    # Add each milestone to the user's set...
                    user = {'id': request.user.id}
                    for milestone in content_milestones:
                        milestones_helpers.add_user_milestone(user, milestone)

    def handle_grade_event(block, event_type, event):  # pylint: disable=unused-argument
        """
        Manages the workflow for recording and updating of student module grade state
        """
        user_id = user.id

        grade = event.get('value')
        max_grade = event.get('max_value')

        set_score(
            user_id,
            descriptor.location,
            grade,
            max_grade,
        )

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(grade, max_grade)

        tags = [
            u"org:{}".format(course_id.org),
            u"course:{}".format(course_id),
            u"score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        dog_stats_api.increment("lms.courseware.question_answered", tags=tags)

        # Cycle through the milestone fulfillment scenarios to see if any are now applicable
        # thanks to the updated grading information that was just submitted
        _fulfill_content_milestones(
            user,
            course_id,
            descriptor.location,
        )

        # Send a signal out to any listeners who are waiting for score change
        # events.
        SCORE_CHANGED.send(
            sender=None,
            points_possible=event['max_value'],
            points_earned=event['value'],
            user_id=user_id,
            course_id=unicode(course_id),
            usage_id=unicode(descriptor.location)
        )

    def publish(block, event_type, event):
        """A function that allows XModules to publish events."""
        if event_type == 'grade' and not is_masquerading_as_specific_student(user, course_id):
            handle_grade_event(block, event_type, event)
        else:
            track_function(event_type, event)

    def rebind_noauth_module_to_user(module, real_user):
        """
        A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser.

        Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler.

        Arguments:
            module (any xblock type):  the module to rebind
            real_user (django.contrib.auth.models.User):  the user to bind to

        Returns:
            nothing (but the side effect is that module is re-bound to real_user)
        """
        if user.is_authenticated():
            err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to "
                       "an anonymous user")
            log.error(err_msg)
            raise LmsModuleRenderError(err_msg)

        field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents(
            course_id,
            real_user,
            module.descriptor,
            asides=XBlockAsidesConfig.possible_asides(),
        )
        student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user))

        (inner_system, inner_student_data) = get_module_system_for_user(
            user=real_user,
            student_data=student_data_real_user,  # These have implicit user bindings, rest of args considered not to
            descriptor=module.descriptor,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

        module.descriptor.bind_for_student(
            inner_system,
            real_user.id,
            [
                partial(OverrideFieldData.wrap, real_user, course),
                partial(LmsFieldData, student_data=inner_student_data),
            ],
        )

        module.descriptor.scope_ids = (
            module.descriptor.scope_ids._replace(user_id=real_user.id)
        )
        module.scope_ids = module.descriptor.scope_ids  # this is needed b/c NamedTuples are immutable
        # now bind the module to the new ModuleSystem instance and vice-versa
        module.runtime = inner_system
        inner_system.xmodule_instance = module

    # Build a list of wrapping functions that will be applied in order
    # to the Fragment content coming out of the xblocks that are about to be rendered.
    block_wrappers = []

    if is_masquerading_as_specific_student(user, course_id):
        block_wrappers.append(filter_displayed_blocks)

    if settings.FEATURES.get("LICENSING", False):
        block_wrappers.append(wrap_with_license)

    # Wrap the output display in a single div to allow for the XModule
    # javascript to be bound correctly
    if wrap_xmodule_display is True:
        block_wrappers.append(partial(
            wrap_xblock,
            'LmsRuntime',
            extra_data={'course-id': course_id.to_deprecated_string()},
            usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string()),
            request_token=request_token,
        ))

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    # Rewrite urls beginning in /static to point to course-specific content
    block_wrappers.append(partial(
        replace_static_urls,
        getattr(descriptor, 'data_dir', None),
        course_id=course_id,
        static_asset_path=static_asset_path or descriptor.static_asset_path
    ))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    block_wrappers.append(partial(replace_course_urls, course_id))

    # this will rewrite intra-courseware links (/jump_to_id/<id>). This format
    # is an improvement over the /course/... format for studio authored courses,
    # because it is agnostic to course-hierarchy.
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work.
    block_wrappers.append(partial(
        replace_jump_to_id_urls,
        course_id,
        reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}),
    ))

    if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
        if is_masquerading_as_specific_student(user, course_id):
            # When masquerading as a specific student, we want to show the debug button
            # unconditionally to enable resetting the state of the student we are masquerading as.
            # We already know the user has staff access when masquerading is active.
            staff_access = True
            # To figure out whether the user has instructor access, we temporarily remove the
            # masquerade_settings from the real_user.  With the masquerading settings in place,
            # the result would always be "False".
            masquerade_settings = user.real_user.masquerade_settings
            del user.real_user.masquerade_settings
            instructor_access = bool(has_access(user.real_user, 'instructor', descriptor, course_id))
            user.real_user.masquerade_settings = masquerade_settings
        else:
            staff_access = has_access(user, 'staff', descriptor, course_id)
            instructor_access = bool(has_access(user, 'instructor', descriptor, course_id))
        if staff_access:
            block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info))

    # These modules store data using the anonymous_student_id as a key.
    # To prevent loss of data, we will continue to provide old modules with
    # the per-student anonymized id (as we have in the past),
    # while giving selected modules a per-course anonymized id.
    # As we have the time to manually test more modules, we can add to the list
    # of modules that get the per-course anonymized id.
    is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor)
    module_class = getattr(descriptor, 'module_class', None)
    is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule)
    if is_pure_xblock or is_lti_module:
        anonymous_student_id = anonymous_id_for_user(user, course_id)
    else:
        anonymous_student_id = anonymous_id_for_user(user, None)

    field_data = LmsFieldData(descriptor._field_data, student_data)  # pylint: disable=protected-access

    user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id))

    system = LmsModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.runtime.resources_fs,
        get_module=inner_get_module,
        user=user,
        debug=settings.DEBUG,
        hostname=settings.SITE_NAME,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(
            static_replace.replace_course_urls,
            course_key=course_id
        ),
        replace_jump_to_id_urls=partial(
            static_replace.replace_jump_to_id_urls,
            course_id=course_id,
            jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''})
        ),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=anonymous_student_id,
        course_id=course_id,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.runtime.mixologist._mixins,  # pylint: disable=protected-access
        wrappers=block_wrappers,
        get_real_user=user_by_anonymous_id,
        services={
            'fs': FSService(),
            'field-data': field_data,
            'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
            "reverification": ReverificationService(),
            'proctoring': ProctoringService(),
            'credit': CreditService(),
            'bookmarks': BookmarksService(user=user),
        },
        get_user_role=lambda: get_user_role(user, course_id),
        descriptor_runtime=descriptor._runtime,  # pylint: disable=protected-access
        rebind_noauth_module_to_user=rebind_noauth_module_to_user,
        user_location=user_location,
        request_token=request_token,
    )

    # pass position specified in URL to module through ModuleSystem
    if position is not None:
        try:
            position = int(position)
        except (ValueError, TypeError):
            log.exception('Non-integer %r passed as position.', position)
            position = None

    system.set('position', position)

    system.set(u'user_is_staff', user_is_staff)
    system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global')))
    system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
    system.set(u'days_early_for_beta', descriptor.days_early_for_beta)

    # make an ErrorDescriptor -- assuming that the descriptor's system is ok
    if has_access(user, u'staff', descriptor.location, course_id):
        system.error_descriptor_class = ErrorDescriptor
    else:
        system.error_descriptor_class = NonStaffErrorDescriptor

    return system, field_data