Example #1
0
class TestMissingStudentModule(TestCase):
    def setUp(self):
        self.user = UserFactory.create(username='******')
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.
        self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_field_from_missing_student_module(self):
        "Test that getting a field from a missing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.get, user_state_key('a_field'))

    def test_set_field_in_missing_student_module(self):
        "Test that setting a field in a missing StudentModule creates the student module"
        self.assertEquals(0, len(self.field_data_cache.cache))
        self.assertEquals(0, StudentModule.objects.all().count())

        self.kvs.set(user_state_key('a_field'), 'a_value')

        self.assertEquals(1, len(self.field_data_cache.cache))
        self.assertEquals(1, StudentModule.objects.all().count())

        student_module = StudentModule.objects.all()[0]
        self.assertEquals({'a_field': 'a_value'}, json.loads(student_module.state))
        self.assertEquals(self.user, student_module.student)
        self.assertEquals(location('usage_id').replace(run=None), student_module.module_state_key)
        self.assertEquals(course_id, student_module.course_id)

    def test_delete_field_from_missing_student_module(self):
        "Test that deleting a field from a missing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.delete, user_state_key('a_field'))

    def test_has_field_for_missing_student_module(self):
        "Test that `has` returns False for missing StudentModules"
        self.assertFalse(self.kvs.has(user_state_key('a_field')))
class TestMissingStudentModule(TestCase):
    def setUp(self):
        self.user = UserFactory.create(username='******')
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.
        self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_field_from_missing_student_module(self):
        "Test that getting a field from a missing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.get, user_state_key('a_field'))

    def test_set_field_in_missing_student_module(self):
        "Test that setting a field in a missing StudentModule creates the student module"
        self.assertEquals(0, len(self.field_data_cache.cache))
        self.assertEquals(0, StudentModule.objects.all().count())

        self.kvs.set(user_state_key('a_field'), 'a_value')

        self.assertEquals(1, len(self.field_data_cache.cache))
        self.assertEquals(1, StudentModule.objects.all().count())

        student_module = StudentModule.objects.all()[0]
        self.assertEquals({'a_field': 'a_value'}, json.loads(student_module.state))
        self.assertEquals(self.user, student_module.student)
        self.assertEquals(location('usage_id').replace(run=None), student_module.module_state_key)
        self.assertEquals(course_id, student_module.course_id)

    def test_delete_field_from_missing_student_module(self):
        "Test that deleting a field from a missing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.delete, user_state_key('a_field'))

    def test_has_field_for_missing_student_module(self):
        "Test that `has` returns False for missing StudentModules"
        self.assertFalse(self.kvs.has(user_state_key('a_field')))
Example #3
0
 def setUp(self):
     self.user = UserFactory.create(username='******')
     self.assertEqual(
         self.user.id,
         1)  # check our assumption hard-coded in the key functions above.
     self.field_data_cache = FieldDataCache([mock_descriptor()], course_id,
                                            self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
class TestMissingStudentModule(TestCase):
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()

        self.user = UserFactory.create(username='******')
        self.assertEqual(
            self.user.id,
            1)  # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()],
                                                   course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_field_from_missing_student_module(self):
        "Test that getting a field from a missing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.get,
                              user_state_key('a_field'))

    def test_set_field_in_missing_student_module(self):
        "Test that setting a field in a missing StudentModule creates the student module"
        self.assertEquals(0, len(self.field_data_cache))
        self.assertEquals(0, StudentModule.objects.all().count())

        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(5):
            self.kvs.set(user_state_key('a_field'), 'a_value')

        self.assertEquals(
            1,
            sum(len(cache) for cache in self.field_data_cache.cache.values()))
        self.assertEquals(1, StudentModule.objects.all().count())

        student_module = StudentModule.objects.all()[0]
        self.assertEquals({'a_field': 'a_value'},
                          json.loads(student_module.state))
        self.assertEquals(self.user, student_module.student)
        self.assertEquals(
            location('usage_id').replace(run=None),
            student_module.module_state_key)
        self.assertEquals(course_id, student_module.course_id)

    def test_delete_field_from_missing_student_module(self):
        "Test that deleting a field from a missing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.delete,
                              user_state_key('a_field'))

    def test_has_field_for_missing_student_module(self):
        "Test that `has` returns False for missing StudentModules"
        with self.assertNumQueries(0):
            self.assertFalse(self.kvs.has(user_state_key('a_field')))
Example #5
0
 def setUp(self):
     student_module = StudentModuleFactory(
         state=json.dumps({
             'a_field': 'a_value',
             'b_field': 'b_value'
         }))
     self.user = student_module.student
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
 def setUp(self):
     field_storage = self.factory.create()
     if hasattr(field_storage, 'student'):
         self.user = field_storage.student
     else:
         self.user = UserFactory.create()
     self.mock_descriptor = mock_descriptor([
         mock_field(self.scope, 'existing_field'),
         mock_field(self.scope, 'other_existing_field')])
     self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #7
0
class TestMissingStudentModule(TestCase):
    # Tell Django to clean out all databases, not just default
    multi_db = True

    def setUp(self):
        super(TestMissingStudentModule, self).setUp()

        self.user = UserFactory.create(username="******")
        self.assertEqual(self.user.id, 1)  # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_field_from_missing_student_module(self):
        "Test that getting a field from a missing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.get, user_state_key("a_field"))

    def test_set_field_in_missing_student_module(self):
        "Test that setting a field in a missing StudentModule creates the student module"
        self.assertEquals(0, len(self.field_data_cache))
        self.assertEquals(0, StudentModule.objects.all().count())

        # We are updating a problem, so we write to courseware_studentmodulehistoryextended
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        # Django 1.8 also has a number of other BEGIN and SAVESTATE queries.
        with self.assertNumQueries(4, using="default"):
            with self.assertNumQueries(1, using="student_module_history"):
                self.kvs.set(user_state_key("a_field"), "a_value")

        self.assertEquals(1, sum(len(cache) for cache in self.field_data_cache.cache.values()))
        self.assertEquals(1, StudentModule.objects.all().count())

        student_module = StudentModule.objects.all()[0]
        self.assertEquals({"a_field": "a_value"}, json.loads(student_module.state))
        self.assertEquals(self.user, student_module.student)
        self.assertEquals(location("usage_id").replace(run=None), student_module.module_state_key)
        self.assertEquals(course_id, student_module.course_id)

    def test_delete_field_from_missing_student_module(self):
        "Test that deleting a field from a missing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.delete, user_state_key("a_field"))

    def test_has_field_for_missing_student_module(self):
        "Test that `has` returns False for missing StudentModules"
        with self.assertNumQueries(0):
            self.assertFalse(self.kvs.has(user_state_key("a_field")))
Example #8
0
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()

        self.user = UserFactory.create(username='******')
        self.assertEqual(
            self.user.id,
            1)  # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()],
                                                   course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #9
0
 def setUp(self):
     student_module = StudentModuleFactory(
         state=json.dumps({
             'a_field': 'a_value',
             'b_field': 'b_value'
         }))
     self.user = student_module.student
     self.assertEqual(
         self.user.id,
         1)  # check our assumption hard-coded in the key functions above.
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #10
0
 def setUp(self):
     field_storage = self.factory.create()
     if hasattr(field_storage, 'student'):
         self.user = field_storage.student
     else:
         self.user = UserFactory.create()
     self.mock_descriptor = mock_descriptor([
         mock_field(self.scope, 'existing_field'),
         mock_field(self.scope, 'other_existing_field')
     ])
     # Each field is stored as a separate row in the table,
     # but we can query them in a single query
     with self.assertNumQueries(1):
         self.field_data_cache = FieldDataCache([self.mock_descriptor],
                                                course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #11
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
Example #12
0
 def setUp(self):
     super(TestInvalidScopes, self).setUp()
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #13
0
    def custom_report_format(*args, **kwargs):
        """
        This method returns the formated answer for the given user and block.
        """
        user = kwargs.get('user')
        block = kwargs.get('block')

        if block and user:
            student_item_dict = block.student_item_key(user=user)
            submission = sub_api.get_submissions(student_item_dict, limit=1)
            try:
                return submission[0].get('answer')
            except IndexError:
                pass

            descriptor = modulestore().get_item(block.location, depth=1)
            field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                block.location.course_key,
                user,
                descriptor,
                asides=XBlockAsidesConfig.possible_asides(),
            )
            student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache))

            if student_data.has(block, FIELD):
                return student_data.get(block, FIELD)
        return ''
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()

        self.user = UserFactory.create(username="******")
        self.assertEqual(self.user.id, 1)  # check our assumption hard-coded in the key functions above.
        self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)
 def setUp(self):
     super(TestStudentModuleStorage, self).setUp()
     student_module = StudentModuleFactory(state=json.dumps({"a_field": "a_value", "b_field": "b_value"}))
     self.user = student_module.student
     self.assertEqual(self.user.id, 1)  # check our assumption hard-coded in the key functions above.
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, "a_field")])], course_id, self.user
     )
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #16
0
    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        self.assertEqual(
            self.user.id,
            1)  # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
                course_id, self.user)

        self.kvs = DjangoKeyValueStore(self.field_data_cache)
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()

        self.user = UserFactory.create(username='******')
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #18
0
 def setUp(self):
     field_storage = self.factory.create()
     if hasattr(field_storage, 'student'):
         self.user = field_storage.student
     else:
         self.user = UserFactory.create()
     self.mock_descriptor = mock_descriptor([
         mock_field(self.scope, 'existing_field'),
         mock_field(self.scope, 'other_existing_field')])
     self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
    def test_invalid_scopes(self):
        for scope in (Scope(user=True, block=BlockScope.DEFINITION),
                      Scope(user=False, block=BlockScope.TYPE),
                      Scope(user=False, block=BlockScope.ALL)):
            key = DjangoKeyValueStore.Key(scope, None, None, 'field')

            self.assertRaises(InvalidScopeError, self.kvs.get, key)
            self.assertRaises(InvalidScopeError, self.kvs.set, key, 'value')
            self.assertRaises(InvalidScopeError, self.kvs.delete, key)
            self.assertRaises(InvalidScopeError, self.kvs.has, key)
            self.assertRaises(InvalidScopeError, self.kvs.set_many, {key: 'value'})
Example #20
0
def _get_module_instance_for_task(course_id,
                                  student,
                                  module_descriptor,
                                  xmodule_instance_args=None,
                                  grade_bucket_type=None,
                                  course=None):
    """
    Fetches a StudentModule instance for a given `course_id`, `student` object, and `module_descriptor`.

    `xmodule_instance_args` is used to provide information for creating a track function and an XQueue callback.
    These are passed, along with `grade_bucket_type`, to get_module_for_descriptor_internal, which sidesteps
    the need for a Request object when instantiating an xmodule instance.
    """
    # reconstitute the problem's corresponding XModule:
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_id, student, module_descriptor)
    student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache))

    # get request-related tracking information from args passthrough, and supplement with task-specific
    # information:
    request_info = xmodule_instance_args.get(
        'request_info', {}) if xmodule_instance_args is not None else {}
    task_info = {
        "student": student.username,
        "task_id": _get_task_id_from_xmodule_args(xmodule_instance_args)
    }

    def make_track_function():
        '''
        Make a tracking function that logs what happened.

        For insertion into ModuleSystem, and used by CapaModule, which will
        provide the event_type (as string) and event (as dict) as arguments.
        The request_info and task_info (and page) are provided here.
        '''
        return lambda event_type, event: task_track(
            request_info, task_info, event_type, event, page='x_module_task')

    xqueue_callback_url_prefix = xmodule_instance_args.get('xqueue_callback_url_prefix', '') \
        if xmodule_instance_args is not None else ''

    return get_module_for_descriptor_internal(
        user=student,
        descriptor=module_descriptor,
        student_data=student_data,
        course_id=course_id,
        track_function=make_track_function(),
        xqueue_callback_url_prefix=xqueue_callback_url_prefix,
        grade_bucket_type=grade_bucket_type,
        # This module isn't being used for front-end rendering
        request_token=None,
        # pass in a loaded course for override enabling
        course=course)
    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()
        student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'}))
        self.user = student_module.student
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user
            )

        self.kvs = DjangoKeyValueStore(self.field_data_cache)
 def setUp(self):
     field_storage = self.factory.create()
     if hasattr(field_storage, 'student'):
         self.user = field_storage.student
     else:
         self.user = UserFactory.create()
     self.mock_descriptor = mock_descriptor([
         mock_field(self.scope, 'existing_field'),
         mock_field(self.scope, 'other_existing_field')])
     # Each field is stored as a separate row in the table,
     # but we can query them in a single query
     with self.assertNumQueries(1):
         self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #23
0
    def _init_field_data_for_block(self, block):
        """
        Initialize the FieldData implementation for the specified XBlock
        """
        if self.user is None:
            # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields
            student_data_store = None
        elif self.user.is_anonymous:
            # The user is anonymous. Future work will support saving their state
            # in a cache or the django session but for now just use a highly
            # ephemeral dict.
            student_data_store = KvsFieldData(kvs=DictKeyValueStore())
        elif self.system.student_data_mode == XBlockRuntimeSystem.STUDENT_DATA_EPHEMERAL:
            # We're in an environment like Studio where we want to let the
            # author test blocks out but not permanently save their state.
            # This in-memory dict will typically only persist for one
            # request-response cycle, so we need to soon replace it with a store
            # that puts the state into a cache or the django session.
            student_data_store = KvsFieldData(kvs=DictKeyValueStore())
        else:
            # Use database-backed field data (i.e. store user_state in StudentModule)
            context_key = block.scope_ids.usage_id.context_key
            if context_key not in self.django_field_data_caches:
                field_data_cache = FieldDataCache(
                    [block],
                    course_id=context_key,
                    user=self.user,
                    asides=None,
                    read_only=False,
                )
                self.django_field_data_caches[context_key] = field_data_cache
            else:
                field_data_cache = self.django_field_data_caches[context_key]
                field_data_cache.add_descriptors_to_cache([block])
            student_data_store = KvsFieldData(
                kvs=DjangoKeyValueStore(field_data_cache))

        return SplitFieldData({
            Scope.content: self.system.authored_data_store,
            Scope.settings: self.system.authored_data_store,
            Scope.parent: self.system.authored_data_store,
            Scope.children: self.system.authored_data_store,
            Scope.user_state_summary: student_data_store,
            Scope.user_state: student_data_store,
            Scope.user_info: student_data_store,
            Scope.preferences: student_data_store,
        })
Example #24
0
def get_module_for_descriptor(user,
                              request,
                              descriptor,
                              field_data_cache,
                              course_key,
                              position=None,
                              wrap_xmodule_display=True,
                              grade_bucket_type=None,
                              static_asset_path='',
                              disable_staff_debug_info=False,
                              course=None):
    """
    Implements get_module, extracting out the request-specific functionality.

    disable_staff_debug_info : If this is True, exclude staff debug information in the rendering of the module.

    See get_module() docstring for further details.
    """
    track_function = make_track_function(request)
    xqueue_callback_url_prefix = get_xqueue_callback_url_prefix(request)

    user_location = getattr(request, 'session', {}).get('country_code')

    student_kvs = DjangoKeyValueStore(field_data_cache)
    if is_masquerading_as_specific_student(user, course_key):
        student_kvs = MasqueradingKeyValueStore(student_kvs, request.session)
    student_data = KvsFieldData(student_kvs)

    return get_module_for_descriptor_internal(
        user=user,
        descriptor=descriptor,
        student_data=student_data,
        course_id=course_key,
        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=xblock_request_token(request),
        disable_staff_debug_info=disable_staff_debug_info,
        course=course)
class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
    """Tests for user_state storage via StudentModule"""
    other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2, location('usage_id'))  # user_id=2, not 1
    existing_field_name = "a_field"

    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()
        student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'}))
        self.user = student_module.student
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user
            )

        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing StudentModule works"
        # This should only read from the cache, not the database
        with self.assertNumQueries(0):
            self.assertEquals('a_value', self.kvs.get(user_state_key('a_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing StudentModule raises a KeyError"
        # This should only read from the cache, not the database
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))

    def test_set_existing_field(self):
        "Test that setting an existing user_state field changes the value"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(3):
            self.kvs.set(user_state_key('a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_set_missing_field(self):
        "Test that setting a new user_state field changes the value"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(3):
            self.kvs.set(user_state_key('not_a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value', 'not_a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it from the StudentModule"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(3):
            self.kvs.delete(user_state_key('a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.delete, user_state_key('not_a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_has_existing_field(self):
        "Test that `has` returns True for existing fields in StudentModules"
        with self.assertNumQueries(0):
            self.assertTrue(self.kvs.has(user_state_key('a_field')))

    def test_has_missing_field(self):
        "Test that `has` returns False for missing fields in StudentModule"
        with self.assertNumQueries(0):
            self.assertFalse(self.kvs.has(user_state_key('not_a_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = user_state_key('field_a')
        key2 = user_state_key('field_b')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        "Test setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()

        # Scope.user_state is stored in a single row in the database, so we only
        # need to send a single update to that table.
        # We also are updating a problem, so we write to courseware student module history
        # We also need to read the database to discover if something other than the
        # DjangoXBlockUserStateClient has written to the StudentModule (such as
        # UserStateCache setting the score on the StudentModule).
        with self.assertNumQueries(3):
            self.kvs.set_many(kv_dict)

        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        "Test failures when setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        # because we're patching the underlying save, we need to ensure the
        # fields are in the cache
        for key in kv_dict:
            self.kvs.set(key, 'test_value')

        with patch('django.db.models.Model.save', side_effect=DatabaseError):
            with self.assertRaises(KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)
        self.assertEquals(exception_context.exception.saved_field_names, [])
 def setUp(self):
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #27
0
class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
    """Tests for user_state storage via StudentModule"""
    other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2,
                                location('usage_id'))  # user_id=2, not 1
    existing_field_name = "a_field"

    def setUp(self):
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        self.assertEqual(
            self.user.id,
            1)  # check our assumption hard-coded in the key functions above.
        self.field_data_cache = FieldDataCache(
            [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
            course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing StudentModule works"
        self.assertEquals('a_value', self.kvs.get(user_state_key('a_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.get,
                          user_state_key('not_a_field'))

    def test_set_existing_field(self):
        "Test that setting an existing user_state field changes the value"
        self.kvs.set(user_state_key('a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({
            'b_field': 'b_value',
            'a_field': 'new_value'
        }, json.loads(StudentModule.objects.all()[0].state))

    def test_set_missing_field(self):
        "Test that setting a new user_state field changes the value"
        self.kvs.set(user_state_key('not_a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals(
            {
                'b_field': 'b_value',
                'a_field': 'a_value',
                'not_a_field': 'new_value'
            }, json.loads(StudentModule.objects.all()[0].state))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it from the StudentModule"
        self.kvs.delete(user_state_key('a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertRaises(KeyError, self.kvs.get,
                          user_state_key('not_a_field'))

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.delete,
                          user_state_key('not_a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({
            'b_field': 'b_value',
            'a_field': 'a_value'
        }, json.loads(StudentModule.objects.all()[0].state))

    def test_has_existing_field(self):
        "Test that `has` returns True for existing fields in StudentModules"
        self.assertTrue(self.kvs.has(user_state_key('a_field')))

    def test_has_missing_field(self):
        "Test that `has` returns False for missing fields in StudentModule"
        self.assertFalse(self.kvs.has(user_state_key('not_a_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = user_state_key('field_a')
        key2 = user_state_key('field_b')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        "Test setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        self.kvs.set_many(kv_dict)

        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        "Test failures when setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        # because we're patching the underlying save, we need to ensure the
        # fields are in the cache
        for key in kv_dict:
            self.kvs.set(key, 'test_value')

        with patch('django.db.models.Model.save', side_effect=DatabaseError):
            with self.assertRaises(
                    KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)
        self.assertEquals(len(exception_context.exception.saved_field_names),
                          0)
 def setUp(self):
     student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'}))
     self.user = student_module.student
     self.field_data_cache = FieldDataCache([mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Example #29
0
class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
    """Tests for user_state storage via StudentModule"""
    other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2, location('usage_id'))  # user_id=2, not 1
    existing_field_name = "a_field"

    def setUp(self):
        student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'}))
        self.user = student_module.student
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.
        self.field_data_cache = FieldDataCache([mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing StudentModule works"
        self.assertEquals('a_value', self.kvs.get(user_state_key('a_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))

    def test_set_existing_field(self):
        "Test that setting an existing user_state field changes the value"
        self.kvs.set(user_state_key('a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_set_missing_field(self):
        "Test that setting a new user_state field changes the value"
        self.kvs.set(user_state_key('not_a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value', 'not_a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it from the StudentModule"
        self.kvs.delete(user_state_key('a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing StudentModule raises a KeyError"
        self.assertRaises(KeyError, self.kvs.delete, user_state_key('not_a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value'}, json.loads(StudentModule.objects.all()[0].state))

    def test_has_existing_field(self):
        "Test that `has` returns True for existing fields in StudentModules"
        self.assertTrue(self.kvs.has(user_state_key('a_field')))

    def test_has_missing_field(self):
        "Test that `has` returns False for missing fields in StudentModule"
        self.assertFalse(self.kvs.has(user_state_key('not_a_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = user_state_key('field_a')
        key2 = user_state_key('field_b')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        "Test setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        self.kvs.set_many(kv_dict)

        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        "Test failures when setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        # because we're patching the underlying save, we need to ensure the
        # fields are in the cache
        for key in kv_dict:
            self.kvs.set(key, 'test_value')

        with patch('django.db.models.Model.save', side_effect=DatabaseError):
            with self.assertRaises(KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)
        self.assertEquals(len(exception_context.exception.saved_field_names), 0)
Example #30
0
class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
    """Tests for user_state storage via StudentModule"""
    other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2,
                                location('usage_id'))  # user_id=2, not 1
    existing_field_name = "a_field"
    # Tell Django to clean out all databases, not just default
    multi_db = True

    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        self.assertEqual(
            self.user.id,
            1)  # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
                course_id, self.user)

        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing StudentModule works"
        # This should only read from the cache, not the database
        with self.assertNumQueries(0):
            self.assertEquals('a_value',
                              self.kvs.get(user_state_key('a_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing StudentModule raises a KeyError"
        # This should only read from the cache, not the database
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.get,
                              user_state_key('not_a_field'))

    def test_set_existing_field(self):
        "Test that setting an existing user_state field changes the value"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(4, using='default'):
            with self.assertNumQueries(1, using='student_module_history'):
                self.kvs.set(user_state_key('a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({
            'b_field': 'b_value',
            'a_field': 'new_value'
        }, json.loads(StudentModule.objects.all()[0].state))

    def test_set_missing_field(self):
        "Test that setting a new user_state field changes the value"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(4, using='default'):
            with self.assertNumQueries(1, using='student_module_history'):
                self.kvs.set(user_state_key('not_a_field'), 'new_value')
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals(
            {
                'b_field': 'b_value',
                'a_field': 'a_value',
                'not_a_field': 'new_value'
            }, json.loads(StudentModule.objects.all()[0].state))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it from the StudentModule"
        # We are updating a problem, so we write to courseware_studentmodulehistory
        # as well as courseware_studentmodule. We also need to read the database
        # to discover if something other than the DjangoXBlockUserStateClient
        # has written to the StudentModule (such as UserStateCache setting the score
        # on the StudentModule).
        with self.assertNumQueries(2, using='default'):
            with self.assertNumQueries(1, using='student_module_history'):
                self.kvs.delete(user_state_key('a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertRaises(KeyError, self.kvs.get,
                          user_state_key('not_a_field'))

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing StudentModule raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.delete,
                              user_state_key('not_a_field'))
        self.assertEquals(1, StudentModule.objects.all().count())
        self.assertEquals({
            'b_field': 'b_value',
            'a_field': 'a_value'
        }, json.loads(StudentModule.objects.all()[0].state))

    def test_has_existing_field(self):
        "Test that `has` returns True for existing fields in StudentModules"
        with self.assertNumQueries(0):
            self.assertTrue(self.kvs.has(user_state_key('a_field')))

    def test_has_missing_field(self):
        "Test that `has` returns False for missing fields in StudentModule"
        with self.assertNumQueries(0):
            self.assertFalse(self.kvs.has(user_state_key('not_a_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = user_state_key('field_a')
        key2 = user_state_key('field_b')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        "Test setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()

        # Scope.user_state is stored in a single row in the database, so we only
        # need to send a single update to that table.
        # We also are updating a problem, so we write to courseware student module history
        # We also need to read the database to discover if something other than the
        # DjangoXBlockUserStateClient has written to the StudentModule (such as
        # UserStateCache setting the score on the StudentModule).
        with self.assertNumQueries(4, using="default"):
            with self.assertNumQueries(1, using="student_module_history"):
                self.kvs.set_many(kv_dict)

        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        "Test failures when setting many fields that are scoped to Scope.user_state"
        kv_dict = self.construct_kv_dict()
        # because we're patching the underlying save, we need to ensure the
        # fields are in the cache
        for key in kv_dict:
            self.kvs.set(key, 'test_value')

        with patch('django.db.models.Model.save', side_effect=DatabaseError):
            with self.assertRaises(
                    KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)
        self.assertEquals(exception_context.exception.saved_field_names, [])
Example #31
0
def get_score(course_id, user, problem_descriptor, module_creator,
              field_data_cache):
    """
    Return the score for a user on a problem, as a tuple (correct, total).
    e.g. (5,7) if you got 5 out of 7 points.

    If this problem doesn't have a score, or we couldn't load it, returns (None,
    None).

    user: a Student object
    problem_descriptor: an XModuleDescriptor
    module_creator: a function that takes a descriptor, and returns the corresponding XModule for this user.
           Can return None if user doesn't have access, or if something else went wrong.
    cache: A FieldDataCache
    """
    if not user.is_authenticated():
        return (None, None)

    # some problems have state that is updated independently of interaction
    # with the LMS, so they need to always be scored. (E.g. foldit.)
    if problem_descriptor.always_recalculate_grades:
        problem = module_creator(problem_descriptor)
        if problem is None:
            return (None, None)
        score = problem.get_score()
        if score is not None:
            return (score['score'], score['total'])
        else:
            return (None, None)

    if not problem_descriptor.has_score:
        # These are not problems, and do not have a score
        return (None, None)

    # Create a fake KeyValueStore key to pull out the StudentModule
    key = DjangoKeyValueStore.Key(Scope.user_state, user.id,
                                  problem_descriptor.location, None)

    student_module = field_data_cache.find(key)

    if student_module is not None and student_module.max_grade is not None:
        correct = student_module.grade if student_module.grade is not None else 0
        total = student_module.max_grade
    else:
        # If the problem was not in the cache, or hasn't been graded yet,
        # we need to instantiate the problem.
        # Otherwise, the max score (cached in student_module) won't be available
        problem = module_creator(problem_descriptor)
        if problem is None:
            return (None, None)

        correct = 0.0
        total = problem.max_score()

        # Problem may be an error module (if something in the problem builder failed)
        # In which case total might be None
        if total is None:
            return (None, None)

    # Now we re-weight the problem, if specified
    weight = problem_descriptor.weight
    if weight is not None:
        if total == 0:
            log.exception(
                "Cannot reweight a problem with zero total points. Problem: " +
                str(student_module))
            return (correct, total)
        correct = correct * weight / total
        total = weight

    return (correct, total)
Example #32
0
def grade(student,
          request,
          course,
          field_data_cache=None,
          keep_raw_scores=False):
    """
    This grades a student as quickly as possible. It returns the
    output from the course grader, augmented with the final letter
    grade. The keys in the output are:

    course: a CourseDescriptor

    - grade : A final letter grade.
    - percent : The final percent for the class (rounded up).
    - section_breakdown : A breakdown of each section that makes
        up the grade. (For display)
    - grade_breakdown : A breakdown of the major components that
        make up the final grade. (For display)
    - keep_raw_scores : if True, then value for key 'raw_scores' contains scores for every graded module

    More information on the format is in the docstring for CourseGrader.
    """

    grading_context = course.grading_context
    raw_scores = []

    if field_data_cache is None:
        field_data_cache = FieldDataCache(grading_context['all_descriptors'],
                                          course.id, student)

    totaled_scores = {}
    # This next complicated loop is just to collect the totaled_scores, which is
    # passed to the grader
    for section_format, sections in grading_context[
            'graded_sections'].iteritems():
        format_scores = []
        for section in sections:
            section_descriptor = section['section_descriptor']
            section_name = section_descriptor.display_name_with_default

            should_grade_section = False
            # If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%
            for moduledescriptor in section['xmoduledescriptors']:
                # some problems have state that is updated independently of interaction
                # with the LMS, so they need to always be scored. (E.g. foldit.)
                if moduledescriptor.always_recalculate_grades:
                    should_grade_section = True
                    break

                # Create a fake key to pull out a StudentModule object from the FieldDataCache

                key = DjangoKeyValueStore.Key(Scope.user_state, student.id,
                                              moduledescriptor.location, None)
                if field_data_cache.find(key):
                    should_grade_section = True
                    break

            if should_grade_section:
                scores = []

                def create_module(descriptor):
                    '''creates an XModule instance given a descriptor'''
                    # TODO: We need the request to pass into here. If we could forego that, our arguments
                    # would be simpler
                    return get_module_for_descriptor(student, request,
                                                     descriptor,
                                                     field_data_cache,
                                                     course.id)

                for module_descriptor in yield_dynamic_descriptor_descendents(
                        section_descriptor, create_module):

                    (correct,
                     total) = get_score(course.id, student, module_descriptor,
                                        create_module, field_data_cache)
                    if correct is None and total is None:
                        continue

                    if settings.GENERATE_PROFILE_SCORES:  # for debugging!
                        if total > 1:
                            correct = random.randrange(max(total - 2, 1),
                                                       total + 1)
                        else:
                            correct = total

                    graded = module_descriptor.graded
                    if not total > 0:
                        #We simply cannot grade a problem that is 12/0, because we might need it as a percentage
                        graded = False

                    scores.append(
                        Score(correct, total, graded,
                              module_descriptor.display_name_with_default))

                _, graded_total = graders.aggregate_scores(
                    scores, section_name)
                if keep_raw_scores:
                    raw_scores += scores
            else:
                graded_total = Score(0.0, 1.0, True, section_name)

            #Add the graded total to totaled_scores
            if graded_total.possible > 0:
                format_scores.append(graded_total)
            else:
                log.exception(
                    "Unable to grade a section with a total possible score of zero. "
                    + str(section_descriptor.location))

        totaled_scores[section_format] = format_scores

    grade_summary = course.grader.grade(
        totaled_scores,
        generate_random_scores=settings.GENERATE_PROFILE_SCORES)

    # We round the grade here, to make sure that the grade is an whole percentage and
    # doesn't get displayed differently than it gets grades
    grade_summary['percent'] = round(grade_summary['percent'] * 100 +
                                     0.05) / 100

    letter_grade = grade_for_percentage(course.grade_cutoffs,
                                        grade_summary['percent'])
    grade_summary['grade'] = letter_grade
    grade_summary[
        'totaled_scores'] = totaled_scores  # make this available, eg for instructor download & debugging
    if keep_raw_scores:
        grade_summary[
            'raw_scores'] = raw_scores  # way to get all RAW scores out to instructor
        # so grader can be double-checked
    return grade_summary
Example #33
0
 def xblock_field_data(descriptor):
     student_data = DbModel(DjangoKeyValueStore(field_data_cache))
     return lms_field_data(descriptor._field_data, student_data)
Example #34
0
class StorageTestBase(object):
    """
    A base class for that gets subclassed when testing each of the scopes.

    """
    # Disable pylint warnings that arise because of the way the child classes call
    # this base class -- pylint's static analysis can't keep up with it.
    # pylint: disable=E1101, E1102

    factory = None
    scope = None
    key_factory = None
    storage_class = None

    def setUp(self):
        field_storage = self.factory.create()
        if hasattr(field_storage, 'student'):
            self.user = field_storage.student
        else:
            self.user = UserFactory.create()
        self.mock_descriptor = mock_descriptor([
            mock_field(self.scope, 'existing_field'),
            mock_field(self.scope, 'other_existing_field')
        ])
        self.field_data_cache = FieldDataCache([self.mock_descriptor],
                                               course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_set_and_get_existing_field(self):
        self.kvs.set(self.key_factory('existing_field'), 'test_value')
        self.assertEquals('test_value',
                          self.kvs.get(self.key_factory('existing_field')))

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing Storage Field works"
        self.assertEquals('old_value',
                          self.kvs.get(self.key_factory('existing_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing Storage Field raises a KeyError"
        self.assertRaises(KeyError, self.kvs.get,
                          self.key_factory('missing_field'))

    def test_set_existing_field(self):
        "Test that setting an existing field changes the value"
        self.kvs.set(self.key_factory('existing_field'), 'new_value')
        self.assertEquals(1, self.storage_class.objects.all().count())
        self.assertEquals(
            'new_value', json.loads(self.storage_class.objects.all()[0].value))

    def test_set_missing_field(self):
        "Test that setting a new field changes the value"
        self.kvs.set(self.key_factory('missing_field'), 'new_value')
        self.assertEquals(2, self.storage_class.objects.all().count())
        self.assertEquals(
            'old_value',
            json.loads(
                self.storage_class.objects.get(
                    field_name='existing_field').value))
        self.assertEquals(
            'new_value',
            json.loads(
                self.storage_class.objects.get(
                    field_name='missing_field').value))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it"
        self.kvs.delete(self.key_factory('existing_field'))
        self.assertEquals(0, self.storage_class.objects.all().count())

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing Storage Field raises a KeyError"
        self.assertRaises(KeyError, self.kvs.delete,
                          self.key_factory('missing_field'))
        self.assertEquals(1, self.storage_class.objects.all().count())

    def test_has_existing_field(self):
        "Test that `has` returns True for an existing Storage Field"
        self.assertTrue(self.kvs.has(self.key_factory('existing_field')))

    def test_has_missing_field(self):
        "Test that `has` return False for an existing Storage Field"
        self.assertFalse(self.kvs.has(self.key_factory('missing_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = self.key_factory('existing_field')
        key2 = self.key_factory('other_existing_field')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        """Test that setting many regular fields at the same time works"""
        kv_dict = self.construct_kv_dict()

        self.kvs.set_many(kv_dict)
        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        """Test that setting many regular fields with a DB error """
        kv_dict = self.construct_kv_dict()
        for key in kv_dict:
            self.kvs.set(key, 'test value')

        with patch('django.db.models.Model.save',
                   side_effect=[None, DatabaseError]):
            with self.assertRaises(
                    KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)

        exception = exception_context.exception
        self.assertEquals(len(exception.saved_field_names), 1)
        self.assertEquals(exception.saved_field_names[0], 'existing_field')
Example #35
0
def get_module_for_descriptor_internal(user,
                                       descriptor,
                                       field_data_cache,
                                       course_id,
                                       track_function,
                                       xqueue_callback_url_prefix,
                                       position=None,
                                       wrap_xmodule_display=True,
                                       grade_bucket_type=None,
                                       static_asset_path=''):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    student_data = DbModel(DjangoKeyValueStore(field_data_cache))
    descriptor._field_data = LmsFieldData(descriptor._field_data, student_data)

    # Setup system context for module instance
    ajax_url = reverse(
        'modx_dispatch',
        kwargs=dict(course_id=course_id,
                    location=descriptor.location.url(),
                    dispatch=''),
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id,
                        userid=str(user.id),
                        mod_id=descriptor.location.url(),
                        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
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    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, descriptor, field_data_cache, course_id, track_function,
            make_xqueue_callback, position, wrap_xmodule_display,
            grade_bucket_type, static_asset_path)

    def publish(event):
        """A function that allows XModules to publish events. This only supports grade changes right now."""
        if event.get('event_name') != 'grade':
            return

        # Construct the key for the module
        key = KeyValueStore.Key(scope=Scope.user_state,
                                user_id=user.id,
                                block_scope_id=descriptor.location,
                                field_name='grade')

        student_module = field_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade,
                                        student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org), "course:{0}".format(course_num),
            "run:{0}".format(run), "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)

    # 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 = []

    # 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(wrap_xblock)

    # 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,
                        'module_id': ''
                    }),
        ))

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, descriptor, 'staff', course_id):
            block_wrappers.append(partial(add_histogram, user))

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        ajax_url=ajax_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_id=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,
                                                'module_id': ''
                                            })),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(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,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location.url()))

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))

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

    descriptor.xmodule_runtime = system
    descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)
    return descriptor
class StorageTestBase(object):
    """
    A base class for that gets subclassed when testing each of the scopes.

    """
    # Disable pylint warnings that arise because of the way the child classes call
    # this base class -- pylint's static analysis can't keep up with it.
    # pylint: disable=no-member, not-callable

    factory = None
    scope = None
    key_factory = None
    storage_class = None

    def setUp(self):
        field_storage = self.factory.create()
        if hasattr(field_storage, 'student'):
            self.user = field_storage.student
        else:
            self.user = UserFactory.create()
        self.mock_descriptor = mock_descriptor([
            mock_field(self.scope, 'existing_field'),
            mock_field(self.scope, 'other_existing_field')])
        # Each field is stored as a separate row in the table,
        # but we can query them in a single query
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)

    def test_set_and_get_existing_field(self):
        with self.assertNumQueries(1):
            self.kvs.set(self.key_factory('existing_field'), 'test_value')
        with self.assertNumQueries(0):
            self.assertEquals('test_value', self.kvs.get(self.key_factory('existing_field')))

    def test_get_existing_field(self):
        "Test that getting an existing field in an existing Storage Field works"
        with self.assertNumQueries(0):
            self.assertEquals('old_value', self.kvs.get(self.key_factory('existing_field')))

    def test_get_missing_field(self):
        "Test that getting a missing field from an existing Storage Field raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.get, self.key_factory('missing_field'))

    def test_set_existing_field(self):
        "Test that setting an existing field changes the value"
        with self.assertNumQueries(1):
            self.kvs.set(self.key_factory('existing_field'), 'new_value')
        self.assertEquals(1, self.storage_class.objects.all().count())
        self.assertEquals('new_value', json.loads(self.storage_class.objects.all()[0].value))

    def test_set_missing_field(self):
        "Test that setting a new field changes the value"
        with self.assertNumQueries(1):
            self.kvs.set(self.key_factory('missing_field'), 'new_value')
        self.assertEquals(2, self.storage_class.objects.all().count())
        self.assertEquals('old_value', json.loads(self.storage_class.objects.get(field_name='existing_field').value))
        self.assertEquals('new_value', json.loads(self.storage_class.objects.get(field_name='missing_field').value))

    def test_delete_existing_field(self):
        "Test that deleting an existing field removes it"
        with self.assertNumQueries(1):
            self.kvs.delete(self.key_factory('existing_field'))
        self.assertEquals(0, self.storage_class.objects.all().count())

    def test_delete_missing_field(self):
        "Test that deleting a missing field from an existing Storage Field raises a KeyError"
        with self.assertNumQueries(0):
            self.assertRaises(KeyError, self.kvs.delete, self.key_factory('missing_field'))
        self.assertEquals(1, self.storage_class.objects.all().count())

    def test_has_existing_field(self):
        "Test that `has` returns True for an existing Storage Field"
        with self.assertNumQueries(0):
            self.assertTrue(self.kvs.has(self.key_factory('existing_field')))

    def test_has_missing_field(self):
        "Test that `has` return False for an existing Storage Field"
        with self.assertNumQueries(0):
            self.assertFalse(self.kvs.has(self.key_factory('missing_field')))

    def construct_kv_dict(self):
        """Construct a kv_dict that can be passed to set_many"""
        key1 = self.key_factory('existing_field')
        key2 = self.key_factory('other_existing_field')
        new_value = 'new value'
        newer_value = 'newer value'
        return {key1: new_value, key2: newer_value}

    def test_set_many(self):
        """Test that setting many regular fields at the same time works"""
        kv_dict = self.construct_kv_dict()

        # Each field is a separate row in the database, hence
        # a separate query
        with self.assertNumQueries(len(kv_dict)):
            self.kvs.set_many(kv_dict)
        for key in kv_dict:
            self.assertEquals(self.kvs.get(key), kv_dict[key])

    def test_set_many_failure(self):
        """Test that setting many regular fields with a DB error """
        kv_dict = self.construct_kv_dict()
        for key in kv_dict:
            with self.assertNumQueries(1):
                self.kvs.set(key, 'test value')

        with patch('django.db.models.Model.save', side_effect=[None, DatabaseError]):
            with self.assertRaises(KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)

        exception = exception_context.exception
        self.assertEquals(exception.saved_field_names, ['existing_field', 'other_existing_field'])
Example #37
0
def get_module_system_for_user(user, field_data_cache,
                               # 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):
    """
    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 field_data_cache, 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
    """
    student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache))

    def make_xqueue_callback(dispatch='score_update'):
        # 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
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    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,
            field_data_cache=field_data_cache,
            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,
        )

    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 settings.FEATURES.get('ENTRANCE_EXAMS', False):
            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 = event.get('user_id', user.id)

        # Construct the key for the module
        key = KeyValueStore.Key(
            scope=Scope.user_state,
            user_id=user_id,
            block_scope_id=descriptor.location,
            field_name='grade'
        )

        student_module = field_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade, student_module.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,
        )

    def publish(block, event_type, event):
        """A function that allows XModules to publish events."""
        if event_type == 'grade':
            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(),
        )

        (inner_system, inner_student_data) = get_module_system_for_user(
            user=real_user,
            field_data_cache=field_data_cache_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
        )
        # rebinds module to a different student.  We'll change system, student_data, and scope_ids
        module.descriptor.bind_for_student(
            inner_system,
            LmsFieldData(module.descriptor._field_data, inner_student_data),  # pylint: disable=protected-access
            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 = []

    # 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 has_access(user, 'staff', descriptor, course_id):
            has_instructor_access = has_access(user, 'instructor', descriptor, course_id)
            block_wrappers.append(partial(add_staff_markup, user, has_instructor_access))

    # 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 = 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,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        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={
            'i18n': ModuleI18nService(),
            'fs': xblock.reference.plugins.FSService(),
            'field-data': field_data,
            'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
        },
        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)
    if settings.FEATURES.get('ENABLE_PSYCHOMETRICS') and user.is_authenticated():
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user, descriptor.location)
        )

    system.set(u'user_is_staff', user_is_staff)
    system.set(u'user_is_admin', 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', getattr(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
Example #38
0
 def setUp(self):
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache([mock_descriptor()], course_id,
                                            self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)