예제 #1
0
class TestMissingStudentModule(TestCase):  # lint-amnesty, pylint: disable=missing-class-docstring
    # Tell Django to clean out all databases, not just default
    databases = {alias for alias in connections}  # lint-amnesty, pylint: disable=unnecessary-comprehension

    def setUp(self):
        super(TestMissingStudentModule, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        self.user = UserFactory.create(username='******')
        assert 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"
        assert 0 == len(self.field_data_cache)
        assert 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')

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

        student_module = StudentModule.objects.all()[0]
        assert {'a_field': 'a_value'} == json.loads(student_module.state)
        assert self.user == student_module.student
        assert location('usage_id').replace(
            run=None) == student_module.module_state_key
        assert 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):
            assert not self.kvs.has(user_state_key('a_field'))
예제 #2
0
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        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)
예제 #3
0
    def setUp(self):
        super().setUp()

        self.user = UserFactory.create(username='******')
        assert 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)
예제 #4
0
    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
        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)
예제 #5
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)
예제 #6
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)
 def setUp(self):
     super().setUp()
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         COURSE_KEY,
         self.user,
     )
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
    def setUp(self):
        super().setUp()
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        assert 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_KEY,
                self.user,
            )

        self.kvs = DjangoKeyValueStore(self.field_data_cache)
예제 #9
0
    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'})
예제 #10
0
파일: module_state.py 프로젝트: saadow123/1
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)
예제 #11
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,
        })
예제 #12
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:
            # This is an anonymous (non-registered) user:
            assert self.user_id.startswith("anon")
            kvs = EphemeralKeyValueStore()
            student_data_store = KvsFieldData(kvs)
        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.
            kvs = EphemeralKeyValueStore()
            student_data_store = KvsFieldData(kvs)
        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.children_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,
        })
class StorageTestBase:
    """
    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_KEY,
                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):
            assert '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):
            assert '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')
        assert 1 == self.storage_class.objects.all().count()
        assert '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')
        assert 2 == self.storage_class.objects.all().count()
        assert 'old_value' == json.loads(
            self.storage_class.objects.get(field_name='existing_field').value)
        assert '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'))
        assert 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'))
        assert 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):
            assert 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):
            assert not 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:
            assert 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 pytest.raises(KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)

        exception = exception_context.value
        assert exception.saved_field_names == [
            'existing_field', 'other_existing_field'
        ]
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
    databases = {alias for alias in connections}  # lint-amnesty, pylint: disable=unnecessary-comprehension

    def setUp(self):
        super().setUp()
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        assert 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_KEY,
                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):
            assert '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')
        assert 1 == StudentModule.objects.all().count()
        assert {
            'b_field': 'b_value',
            'a_field': 'new_value'
        } == json.loads(StudentModule.objects.all()[0].state)
        # lint-amnesty, pylint: disable=line-too-long

    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')
        assert 1 == StudentModule.objects.all().count()
        assert {
            'b_field': 'b_value',
            'a_field': 'a_value',
            'not_a_field': 'new_value'
        } == json.loads(StudentModule.objects.all()[0].state)
        # lint-amnesty, pylint: disable=line-too-long

    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'))
        assert 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'))
        assert 1 == StudentModule.objects.all().count()
        assert {
            '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):
            assert 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):
            assert not 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:
            assert 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 pytest.raises(KeyValueMultiSaveError) as exception_context:
                self.kvs.set_many(kv_dict)
        assert exception_context.value.saved_field_names == []
예제 #15
0
 def setUp(self):
     super(TestInvalidScopes, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache([mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user)  # lint-amnesty, pylint: disable=line-too-long
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
예제 #16
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')))