Commit 993c24b7 by Calen Pennington

WIP: Model data caching work

parent 507d2021
...@@ -358,7 +358,7 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -358,7 +358,7 @@ class CourseDescriptor(SequenceDescriptor):
all_descriptors - This contains a list of all xmodules that can all_descriptors - This contains a list of all xmodules that can
effect grading a student. This is used to efficiently fetch effect grading a student. This is used to efficiently fetch
all the xmodule state for a StudentModuleCache without walking all the xmodule state for a ModelDataCache without walking
the descriptor tree again. the descriptor tree again.
......
...@@ -19,10 +19,10 @@ from xmodule.contentstore.content import StaticContent ...@@ -19,10 +19,10 @@ from xmodule.contentstore.content import StaticContent
from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.xml import XMLModuleStore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.x_module import XModule from xmodule.x_module import XModule
from courseware.model_data import ModelDataCache
from static_replace import replace_urls, try_staticfiles_lookup from static_replace import replace_urls, try_staticfiles_lookup
from courseware.access import has_access from courseware.access import has_access
import branding import branding
from courseware.models import StudentModuleCache
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -148,12 +148,23 @@ def get_course_about_section(course, section_key): ...@@ -148,12 +148,23 @@ def get_course_about_section(course, section_key):
request = get_request_for_thread() request = get_request_for_thread()
loc = course.location._replace(category='about', name=section_key) loc = course.location._replace(category='about', name=section_key)
course_module = get_module(request.user, request, loc, None, course.id, not_found_ok = True, wrap_xmodule_display = True)
# Use an empty cache
model_data_cache = ModelDataCache([], course.id, request.user)
about_module = get_module(
request.user,
request,
loc,
model_data_cache,
course.id,
not_found_ok=True,
wrap_xmodule_display=True
)
html = '' html = ''
if course_module is not None: if about_module is not None:
html = course_module.get_html() html = about_module.get_html()
return html return html
...@@ -174,7 +185,7 @@ def get_course_about_section(course, section_key): ...@@ -174,7 +185,7 @@ def get_course_about_section(course, section_key):
def get_course_info_section(request, cache, course, section_key): def get_course_info_section(request, course, section_key):
""" """
This returns the snippet of html to be rendered on the course info page, This returns the snippet of html to be rendered on the course info page,
given the key for the section. given the key for the section.
...@@ -188,11 +199,22 @@ def get_course_info_section(request, cache, course, section_key): ...@@ -188,11 +199,22 @@ def get_course_info_section(request, cache, course, section_key):
loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key) loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key)
course_module = get_module(request.user, request, loc, cache, course.id, wrap_xmodule_display = True)
# Use an empty cache
model_data_cache = ModelDataCache([], course.id, request.user)
info_module = get_module(
request.user,
request,
loc,
model_data_cache,
course.id,
wrap_xmodule_display=True
)
html = '' html = ''
if course_module is not None: if info_module is not None:
html = course_module.get_html() html = info_module.get_html()
return html return html
......
...@@ -13,7 +13,7 @@ import xmodule ...@@ -13,7 +13,7 @@ import xmodule
import mitxmako.middleware as middleware import mitxmako.middleware as middleware
middleware.MakoMiddleware() middleware.MakoMiddleware()
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from courseware.models import StudentModuleCache from courseware.model_data import ModelDataCache
from courseware.module_render import get_module from courseware.module_render import get_module
...@@ -83,7 +83,7 @@ class Command(BaseCommand): ...@@ -83,7 +83,7 @@ class Command(BaseCommand):
# TODO (cpennington): Get coursename in a legitimate way # TODO (cpennington): Get coursename in a legitimate way
course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012' course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012'
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( student_module_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id, course_id,
sample_user, modulestore().get_item(course_location)) sample_user, modulestore().get_item(course_location))
course = get_module(sample_user, None, course_location, student_module_cache) course = get_module(sample_user, None, course_location, student_module_cache)
......
...@@ -198,106 +198,3 @@ class XModuleStudentInfoField(models.Model): ...@@ -198,106 +198,3 @@ class XModuleStudentInfoField(models.Model):
def __unicode__(self): def __unicode__(self):
return unicode(repr(self)) return unicode(repr(self))
# TODO (cpennington): Remove these once the LMS switches to using XModuleDescriptors
class StudentModuleCache(object):
"""
A cache of StudentModules for a specific student
"""
def __init__(self, course_id, user, descriptors, select_for_update=False):
'''
Find any StudentModule objects that are needed by any descriptor
in descriptors. Avoids making multiple queries to the database.
Note: Only modules that have store_state = True or have shared
state will have a StudentModule.
Arguments
user: The user for which to fetch maching StudentModules
descriptors: An array of XModuleDescriptors.
select_for_update: Flag indicating whether the rows should be locked until end of transaction
'''
if user.is_authenticated():
module_ids = self._get_module_state_keys(descriptors)
# This works around a limitation in sqlite3 on the number of parameters
# that can be put into a single query
self.cache = []
chunk_size = 500
for id_chunk in [module_ids[i:i + chunk_size] for i in xrange(0, len(module_ids), chunk_size)]:
if select_for_update:
self.cache.extend(StudentModule.objects.select_for_update().filter(
course_id=course_id,
student=user,
module_state_key__in=id_chunk)
)
else:
self.cache.extend(StudentModule.objects.filter(
course_id=course_id,
student=user,
module_state_key__in=id_chunk)
)
else:
self.cache = []
@classmethod
def cache_for_descriptor_descendents(cls, course_id, user, descriptor, depth=None,
descriptor_filter=lambda descriptor: True,
select_for_update=False):
"""
course_id: the course in the context of which we want StudentModules.
user: the django user for whom to load modules.
descriptor: An XModuleDescriptor
depth is the number of levels of descendent modules to load StudentModules for, in addition to
the supplied descriptor. If depth is None, load all descendent StudentModules
descriptor_filter is a function that accepts a descriptor and return wether the StudentModule
should be cached
select_for_update: Flag indicating whether the rows should be locked until end of transaction
"""
def get_child_descriptors(descriptor, depth, descriptor_filter):
if descriptor_filter(descriptor):
descriptors = [descriptor]
else:
descriptors = []
if depth is None or depth > 0:
new_depth = depth - 1 if depth is not None else depth
for child in descriptor.get_children():
descriptors.extend(get_child_descriptors(child, new_depth, descriptor_filter))
return descriptors
descriptors = get_child_descriptors(descriptor, depth, descriptor_filter)
return StudentModuleCache(course_id, user, descriptors, select_for_update)
def _get_module_state_keys(self, descriptors):
'''
Get a list of the state_keys needed for StudentModules
required for this module descriptor
descriptor_filter is a function that accepts a descriptor and return wether the StudentModule
should be cached
'''
return [descriptor.location.url() for descriptor in descriptors if descriptor.stores_state]
def lookup(self, course_id, module_type, module_state_key):
'''
Look for a student module with the given course_id, type, and id in the cache.
cache -- list of student modules
returns first found object, or None
'''
for o in self.cache:
if (o.course_id == course_id and
o.module_type == module_type and
o.module_state_key == module_state_key):
return o
return None
def append(self, student_module):
self.cache.append(student_module)
...@@ -17,7 +17,7 @@ from capa.xqueue_interface import XQueueInterface ...@@ -17,7 +17,7 @@ from capa.xqueue_interface import XQueueInterface
from capa.chem import chemcalc from capa.chem import chemcalc
from courseware.access import has_access from courseware.access import has_access
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache from models import StudentModule
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from static_replace import replace_urls from static_replace import replace_urls
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
...@@ -28,7 +28,7 @@ from xmodule.x_module import ModuleSystem ...@@ -28,7 +28,7 @@ from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xblock.runtime import DbModel from xblock.runtime import DbModel
from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule
from .model_data import LmsKeyValueStore, LmsUsage from .model_data import LmsKeyValueStore, LmsUsage, ModelDataCache
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from statsd import statsd from statsd import statsd
...@@ -60,7 +60,7 @@ def make_track_function(request): ...@@ -60,7 +60,7 @@ def make_track_function(request):
return f return f
def toc_for_course(user, request, course, active_chapter, active_section): def toc_for_course(user, request, course, active_chapter, active_section, model_data_cache):
''' '''
Create a table of contents from the module store Create a table of contents from the module store
...@@ -80,11 +80,11 @@ def toc_for_course(user, request, course, active_chapter, active_section): ...@@ -80,11 +80,11 @@ def toc_for_course(user, request, course, active_chapter, active_section):
NOTE: assumes that if we got this far, user has access to course. Returns NOTE: assumes that if we got this far, user has access to course. Returns
None if this is not the case. None if this is not the case.
model_data_cache must include data from the course module and 2 levels of its descendents
''' '''
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( course_module = get_module(user, request, course.location, model_data_cache, course.id)
course.id, user, course, depth=2)
course_module = get_module(user, request, course.location, student_module_cache, course.id)
if course_module is None: if course_module is None:
return None return None
...@@ -115,7 +115,7 @@ def toc_for_course(user, request, course, active_chapter, active_section): ...@@ -115,7 +115,7 @@ def toc_for_course(user, request, course, active_chapter, active_section):
return chapters return chapters
def get_module(user, request, location, student_module_cache, course_id, def get_module(user, request, location, model_data_cache, course_id,
position=None, not_found_ok = False, wrap_xmodule_display=True, position=None, not_found_ok = False, wrap_xmodule_display=True,
grade_bucket_type=None): grade_bucket_type=None):
""" """
...@@ -128,7 +128,7 @@ def get_module(user, request, location, student_module_cache, course_id, ...@@ -128,7 +128,7 @@ def get_module(user, request, location, student_module_cache, course_id,
- request : current django HTTPrequest. Note: request.user isn't used for anything--all auth - request : current django HTTPrequest. Note: request.user isn't used for anything--all auth
and such works based on user. and such works based on user.
- location : A Location-like object identifying the module to load - location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache - model_data_cache : a ModelDataCache
- course_id : the course_id in the context of which to load module - course_id : the course_id in the context of which to load module
- position : extra information from URL for user-specified - position : extra information from URL for user-specified
position within module position within module
...@@ -138,7 +138,7 @@ def get_module(user, request, location, student_module_cache, course_id, ...@@ -138,7 +138,7 @@ def get_module(user, request, location, student_module_cache, course_id,
if possible. If not possible, return None. if possible. If not possible, return None.
""" """
try: try:
return _get_module(user, request, location, student_module_cache, course_id, position, wrap_xmodule_display) return _get_module(user, request, location, model_data_cache, course_id, position, wrap_xmodule_display)
except ItemNotFoundError: except ItemNotFoundError:
if not not_found_ok: if not not_found_ok:
log.exception("Error in get_module") log.exception("Error in get_module")
...@@ -149,7 +149,7 @@ def get_module(user, request, location, student_module_cache, course_id, ...@@ -149,7 +149,7 @@ def get_module(user, request, location, student_module_cache, course_id,
return None return None
def _get_module(user, request, location, student_module_cache, course_id, def _get_module(user, request, location, model_data_cache, course_id,
position=None, wrap_xmodule_display=True, grade_bucket_type=None): position=None, wrap_xmodule_display=True, grade_bucket_type=None):
""" """
Actually implement get_module. See docstring there for details. Actually implement get_module. See docstring there for details.
...@@ -204,11 +204,11 @@ def _get_module(user, request, location, student_module_cache, course_id, ...@@ -204,11 +204,11 @@ def _get_module(user, request, location, student_module_cache, course_id,
Delegate to get_module. It does an access check, so may return None Delegate to get_module. It does an access check, so may return None
""" """
return get_module(user, request, location, return get_module(user, request, location,
student_module_cache, course_id, position) model_data_cache, course_id, position)
def xblock_model_data(descriptor): def xblock_model_data(descriptor):
return DbModel( return DbModel(
LmsKeyValueStore(course_id, user, descriptor._model_data, student_module_cache), LmsKeyValueStore(descriptor._model_data, model_data_cache),
descriptor.module_class, descriptor.module_class,
user.id, user.id,
LmsUsage(location, location) LmsUsage(location, location)
...@@ -218,18 +218,13 @@ def _get_module(user, request, location, student_module_cache, course_id, ...@@ -218,18 +218,13 @@ def _get_module(user, request, location, student_module_cache, course_id,
if event.get('event_name') != 'grade': if event.get('event_name') != 'grade':
return return
student_module = student_module_cache.lookup( student_module, created = StudentModule.objects.get_or_create(
course_id, descriptor.location.category, descriptor.location.url() course_id=course_id,
student=user,
module_type=descriptor.location.category,
module_state_key=descriptor.location.url(),
defaults={'state': '{}'},
) )
if student_module is None:
student_module = StudentModule(
course_id=course_id,
student=user,
module_type=descriptor.location.category,
module_state_key=descriptor.location.url(),
state=json.dumps({})
)
student_module_cache.append(student_module)
student_module.grade = event.get('value') student_module.grade = event.get('value')
student_module.max_grade = event.get('max_value') student_module.max_grade = event.get('max_value')
student_module.save() student_module.save()
...@@ -335,9 +330,9 @@ def xqueue_callback(request, course_id, userid, id, dispatch): ...@@ -335,9 +330,9 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
# Retrieve target StudentModule # Retrieve target StudentModule
user = User.objects.get(id=userid) user = User.objects.get(id=userid)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id, model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id,
user, modulestore().get_instance(course_id, id), depth=0, select_for_update=True) user, modulestore().get_instance(course_id, id), depth=0, select_for_update=True)
instance = get_module(user, request, id, student_module_cache, course_id, grade_bucket_type='xqueue') instance = get_module(user, request, id, model_data_cache, course_id, grade_bucket_type='xqueue')
if instance is None: if instance is None:
log.debug("No module {0} for user {1}--access denied?".format(id, user)) log.debug("No module {0} for user {1}--access denied?".format(id, user))
raise Http404 raise Http404
...@@ -394,10 +389,10 @@ def modx_dispatch(request, dispatch, location, course_id): ...@@ -394,10 +389,10 @@ def modx_dispatch(request, dispatch, location, course_id):
return HttpResponse(json.dumps({'success': file_too_big_msg})) return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles p[fileinput_id] = inputfiles
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id, model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id,
request.user, modulestore().get_instance(course_id, location)) request.user, modulestore().get_instance(course_id, location))
instance = get_module(request.user, request, location, student_module_cache, course_id, grade_bucket_type='ajax') instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax')
if instance is None: if instance is None:
# Either permissions just changed, or someone is trying to be clever # Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to. # and load something they shouldn't have access to.
......
...@@ -5,18 +5,26 @@ from django.contrib.auth.models import User ...@@ -5,18 +5,26 @@ from django.contrib.auth.models import User
from functools import partial from functools import partial
from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError, ModelDataCache
from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField, StudentModuleCache from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField
from xblock.core import Scope, BlockScope from xblock.core import Scope, BlockScope
from xmodule.modulestore import Location from xmodule.modulestore import Location
from django.test import TestCase from django.test import TestCase
def mock_descriptor(): def mock_field(scope, name):
field = Mock()
field.scope = scope
field.name = name
return field
def mock_descriptor(fields=[], lms_fields=[]):
descriptor = Mock() descriptor = Mock()
descriptor.stores_state = True descriptor.stores_state = True
descriptor.location = location('def_id') descriptor.location = location('def_id')
descriptor.module_class.fields = fields
descriptor.module_class.lms.fields = lms_fields
return descriptor return descriptor
location = partial(Location, 'i4x', 'edX', 'test_course', 'problem') location = partial(Location, 'i4x', 'edX', 'test_course', 'problem')
...@@ -85,7 +93,7 @@ class TestDescriptorFallback(TestCase): ...@@ -85,7 +93,7 @@ class TestDescriptorFallback(TestCase):
'field_a': 'content', 'field_a': 'content',
'field_b': 'settings', 'field_b': 'settings',
} }
self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None) self.kvs = LmsKeyValueStore(self.desc_md, None)
def test_get_from_descriptor(self): def test_get_from_descriptor(self):
self.assertEquals('content', self.kvs.get(content_key('field_a'))) self.assertEquals('content', self.kvs.get(content_key('field_a')))
...@@ -103,13 +111,11 @@ class TestDescriptorFallback(TestCase): ...@@ -103,13 +111,11 @@ class TestDescriptorFallback(TestCase):
self.assertEquals('settings', self.desc_md['field_b']) self.assertEquals('settings', self.desc_md['field_b'])
class TestStudentStateFields(TestCase):
pass
class TestInvalidScopes(TestCase): class TestInvalidScopes(TestCase):
def setUp(self): def setUp(self):
self.desc_md = {} self.desc_md = {}
self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None) self.mdc = ModelDataCache([mock_descriptor([mock_field(Scope.student_state, 'a_field')])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_invalid_scopes(self): def test_invalid_scopes(self):
for scope in (Scope(student=True, block=BlockScope.DEFINITION), for scope in (Scope(student=True, block=BlockScope.DEFINITION),
...@@ -123,12 +129,10 @@ class TestInvalidScopes(TestCase): ...@@ -123,12 +129,10 @@ class TestInvalidScopes(TestCase):
class TestStudentModuleStorage(TestCase): class TestStudentModuleStorage(TestCase):
def setUp(self): def setUp(self):
student_module = StudentModuleFactory.create(state=json.dumps({'a_field': 'a_value'}))
self.user = student_module.student
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [mock_descriptor()]) self.mdc = Mock()
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) self.mdc.find.return_value.state = json.dumps({'a_field': 'a_value'})
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self): def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentModule works" "Test that getting an existing field in an existing StudentModule works"
...@@ -154,7 +158,7 @@ class TestStudentModuleStorage(TestCase): ...@@ -154,7 +158,7 @@ class TestStudentModuleStorage(TestCase):
"Test that deleting an existing field removes it from the StudentModule" "Test that deleting an existing field removes it from the StudentModule"
self.kvs.delete(student_state_key('a_field')) self.kvs.delete(student_state_key('a_field'))
self.assertEquals(1, StudentModule.objects.all().count()) self.assertEquals(1, StudentModule.objects.all().count())
self.assertEquals({}, json.loads(StudentModule.objects.all()[0].state)) self.assertEquals({}, self.mdc.find.return_value.state)
def test_delete_missing_field(self): def test_delete_missing_field(self):
"Test that deleting a missing field from an existing StudentModule raises a KeyError" "Test that deleting a missing field from an existing StudentModule raises a KeyError"
...@@ -167,8 +171,8 @@ class TestMissingStudentModule(TestCase): ...@@ -167,8 +171,8 @@ class TestMissingStudentModule(TestCase):
def setUp(self): def setUp(self):
self.user = UserFactory.create() self.user = UserFactory.create()
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [mock_descriptor()]) self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_field_from_missing_student_module(self): def test_get_field_from_missing_student_module(self):
"Test that getting a field from a missing StudentModule raises a KeyError" "Test that getting a field from a missing StudentModule raises a KeyError"
...@@ -176,12 +180,12 @@ class TestMissingStudentModule(TestCase): ...@@ -176,12 +180,12 @@ class TestMissingStudentModule(TestCase):
def test_set_field_in_missing_student_module(self): def test_set_field_in_missing_student_module(self):
"Test that setting a field in a missing StudentModule creates the student module" "Test that setting a field in a missing StudentModule creates the student module"
self.assertEquals(0, len(self.smc.cache)) self.assertEquals(0, len(self.mdc.cache))
self.assertEquals(0, StudentModule.objects.all().count()) self.assertEquals(0, StudentModule.objects.all().count())
self.kvs.set(student_state_key('a_field'), 'a_value') self.kvs.set(student_state_key('a_field'), 'a_value')
self.assertEquals(1, len(self.smc.cache)) self.assertEquals(1, len(self.mdc.cache))
self.assertEquals(1, StudentModule.objects.all().count()) self.assertEquals(1, StudentModule.objects.all().count())
student_module = StudentModule.objects.all()[0] student_module = StudentModule.objects.all()[0]
...@@ -201,8 +205,8 @@ class TestSettingsStorage(TestCase): ...@@ -201,8 +205,8 @@ class TestSettingsStorage(TestCase):
settings = SettingsFactory.create() settings = SettingsFactory.create()
self.user = UserFactory.create() self.user = UserFactory.create()
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, []) self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self): def test_get_existing_field(self):
"Test that getting an existing field in an existing SettingsField works" "Test that getting an existing field in an existing SettingsField works"
...@@ -242,8 +246,8 @@ class TestContentStorage(TestCase): ...@@ -242,8 +246,8 @@ class TestContentStorage(TestCase):
content = ContentFactory.create() content = ContentFactory.create()
self.user = UserFactory.create() self.user = UserFactory.create()
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, []) self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self): def test_get_existing_field(self):
"Test that getting an existing field in an existing ContentField works" "Test that getting an existing field in an existing ContentField works"
...@@ -283,8 +287,11 @@ class TestStudentPrefsStorage(TestCase): ...@@ -283,8 +287,11 @@ class TestStudentPrefsStorage(TestCase):
student_pref = StudentPrefsFactory.create() student_pref = StudentPrefsFactory.create()
self.user = student_pref.student self.user = student_pref.student
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, []) self.mdc = ModelDataCache([mock_descriptor([
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) mock_field(Scope.student_preferences, 'student_pref_field'),
mock_field(Scope.student_preferences, 'not_student_pref_field'),
])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self): def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentPrefsField works" "Test that getting an existing field in an existing StudentPrefsField works"
...@@ -309,7 +316,6 @@ class TestStudentPrefsStorage(TestCase): ...@@ -309,7 +316,6 @@ class TestStudentPrefsStorage(TestCase):
def test_delete_existing_field(self): def test_delete_existing_field(self):
"Test that deleting an existing field removes it" "Test that deleting an existing field removes it"
print list(XModuleStudentPrefsField.objects.all())
self.kvs.delete(student_prefs_key('student_pref_field')) self.kvs.delete(student_prefs_key('student_pref_field'))
self.assertEquals(0, XModuleStudentPrefsField.objects.all().count()) self.assertEquals(0, XModuleStudentPrefsField.objects.all().count())
...@@ -325,8 +331,11 @@ class TestStudentInfoStorage(TestCase): ...@@ -325,8 +331,11 @@ class TestStudentInfoStorage(TestCase):
student_info = StudentInfoFactory.create() student_info = StudentInfoFactory.create()
self.user = student_info.student self.user = student_info.student
self.desc_md = {} self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, []) self.mdc = ModelDataCache([mock_descriptor([
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc) mock_field(Scope.student_info, 'student_info_field'),
mock_field(Scope.student_info, 'not_student_info_field'),
])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self): def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentInfoField works" "Test that getting an existing field in an existing StudentInfoField works"
......
...@@ -23,7 +23,7 @@ import xmodule.modulestore.django ...@@ -23,7 +23,7 @@ import xmodule.modulestore.django
# Need access to internal func to put users in the right group # Need access to internal func to put users in the right group
from courseware import grades from courseware import grades
from courseware.access import _course_staff_group_name from courseware.access import _course_staff_group_name
from courseware.models import StudentModuleCache from courseware.model_data import ModelDataCache
from student.models import Registration from student.models import Registration
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -705,27 +705,27 @@ class TestCourseGrader(PageLoader): ...@@ -705,27 +705,27 @@ class TestCourseGrader(PageLoader):
self.factory = RequestFactory() self.factory = RequestFactory()
def get_grade_summary(self): def get_grade_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course) self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress', fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id})) kwargs={'course_id': self.graded_course.id}))
return grades.grade(self.student_user, fake_request, return grades.grade(self.student_user, fake_request,
self.graded_course, student_module_cache) self.graded_course, model_data_cache)
def get_homework_scores(self): def get_homework_scores(self):
return self.get_grade_summary()['totaled_scores']['Homework'] return self.get_grade_summary()['totaled_scores']['Homework']
def get_progress_summary(self): def get_progress_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course) self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress', fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id})) kwargs={'course_id': self.graded_course.id}))
progress_summary = grades.progress_summary(self.student_user, fake_request, progress_summary = grades.progress_summary(self.student_user, fake_request,
self.graded_course, student_module_cache) self.graded_course, model_data_cache)
return progress_summary return progress_summary
def check_grade_percent(self, percent): def check_grade_percent(self, percent):
......
...@@ -23,7 +23,7 @@ from courseware import grades ...@@ -23,7 +23,7 @@ from courseware import grades
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import (get_course_with_access, get_courses_by_university) from courseware.courses import (get_course_with_access, get_courses_by_university)
import courseware.tabs as tabs import courseware.tabs as tabs
from courseware.models import StudentModuleCache from courseware.model_data import ModelDataCache
from module_render import toc_for_course, get_module from module_render import toc_for_course, get_module
from student.models import UserProfile from student.models import UserProfile
...@@ -81,7 +81,7 @@ def courses(request): ...@@ -81,7 +81,7 @@ def courses(request):
return render_to_response("courses.html", {'universities': universities}) return render_to_response("courses.html", {'universities': universities})
def render_accordion(request, course, chapter, section): def render_accordion(request, course, chapter, section, model_data_cache):
''' Draws navigation bar. Takes current position in accordion as ''' Draws navigation bar. Takes current position in accordion as
parameter. parameter.
...@@ -92,7 +92,7 @@ def render_accordion(request, course, chapter, section): ...@@ -92,7 +92,7 @@ def render_accordion(request, course, chapter, section):
Returns the html string''' Returns the html string'''
# grab the table of contents # grab the table of contents
toc = toc_for_course(request.user, request, course, chapter, section) toc = toc_for_course(request.user, request, course, chapter, section, model_data_cache)
context = dict([('toc', toc), context = dict([('toc', toc),
('course_id', course.id), ('course_id', course.id),
...@@ -191,24 +191,21 @@ def index(request, course_id, chapter=None, section=None, ...@@ -191,24 +191,21 @@ def index(request, course_id, chapter=None, section=None,
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course.id]))
try: try:
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course.id, request.user, course, depth=2) course.id, request.user, course, depth=2)
# Has this student been in this course before? course_module = get_module(request.user, request, course.location, model_data_cache, course.id)
first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None
course_module = get_module(request.user, request, course.location, student_module_cache, course.id)
if course_module is None: if course_module is None:
log.warning('If you see this, something went wrong: if we got this' log.warning('If you see this, something went wrong: if we got this'
' far, should have gotten a course module for this user') ' far, should have gotten a course module for this user')
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course.id]))
if chapter is None: if chapter is None:
return redirect_to_course_position(course_module, first_time) return redirect_to_course_position(course_module, course_module.first_time_user)
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'accordion': render_accordion(request, course, chapter, section), 'accordion': render_accordion(request, course, chapter, section, model_data_cache),
'COURSE_TITLE': course.title, 'COURSE_TITLE': course.title,
'course': course, 'course': course,
'init': '', 'init': '',
...@@ -224,7 +221,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -224,7 +221,7 @@ def index(request, course_id, chapter=None, section=None,
raise Http404 raise Http404
chapter_module = get_module(request.user, request, chapter_descriptor.location, chapter_module = get_module(request.user, request, chapter_descriptor.location,
student_module_cache, course_id) model_data_cache, course_id)
if chapter_module is None: if chapter_module is None:
# User may be trying to access a chapter that isn't live yet # User may be trying to access a chapter that isn't live yet
raise Http404 raise Http404
...@@ -235,11 +232,11 @@ def index(request, course_id, chapter=None, section=None, ...@@ -235,11 +232,11 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist # Specifically asked-for section doesn't exist
raise Http404 raise Http404
section_student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( section_model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id, request.user, section_descriptor) course_id, request.user, section_descriptor)
section_module = get_module(request.user, request, section_module = get_module(request.user, request,
section_descriptor.location, section_descriptor.location,
section_student_module_cache, course_id, position) section_model_data_cache, course_id, position)
if section_module is None: if section_module is None:
# User may be trying to be clever and access something # User may be trying to be clever and access something
# they don't have access to. # they don't have access to.
...@@ -491,12 +488,12 @@ def progress(request, course_id, student_id=None): ...@@ -491,12 +488,12 @@ def progress(request, course_id, student_id=None):
# additional DB lookup (this kills the Progress page in particular). # additional DB lookup (this kills the Progress page in particular).
student = User.objects.prefetch_related("groups").get(id=student.id) student = User.objects.prefetch_related("groups").get(id=student.id)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id, student, course) course_id, student, course)
courseware_summary = grades.progress_summary(student, request, course, courseware_summary = grades.progress_summary(student, request, course,
student_module_cache) model_data_cache)
grade_summary = grades.grade(student, request, course, student_module_cache) grade_summary = grades.grade(student, request, course, model_data_cache)
if courseware_summary is None: if courseware_summary is None:
#This means the student didn't have access to the course (which the instructor requested) #This means the student didn't have access to the course (which the instructor requested)
......
...@@ -27,20 +27,20 @@ $(document).ready(function(){ ...@@ -27,20 +27,20 @@ $(document).ready(function(){
% if user.is_authenticated(): % if user.is_authenticated():
<section class="updates"> <section class="updates">
<h1>Course Updates &amp; News</h1> <h1>Course Updates &amp; News</h1>
${get_course_info_section(request, cache, course, 'updates')} ${get_course_info_section(request, course, 'updates')}
</section> </section>
<section aria-label="Handout Navigation" class="handouts"> <section aria-label="Handout Navigation" class="handouts">
<h1>${course.info_sidebar_name}</h1> <h1>${course.info_sidebar_name}</h1>
${get_course_info_section(request, cache, course, 'handouts')} ${get_course_info_section(request, course, 'handouts')}
</section> </section>
% else: % else:
<section class="updates"> <section class="updates">
<h1>Course Updates &amp; News</h1> <h1>Course Updates &amp; News</h1>
${get_course_info_section(request, cache, course, 'guest_updates')} ${get_course_info_section(request, course, 'guest_updates')}
</section> </section>
<section aria-label="Handout Navigation" class="handouts"> <section aria-label="Handout Navigation" class="handouts">
<h1>Course Handouts</h1> <h1>Course Handouts</h1>
${get_course_info_section(request, cache, course, 'guest_handouts')} ${get_course_info_section(request, course, 'guest_handouts')}
</section> </section>
% endif % endif
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment