Merge pull request #1252 from MITx/feature/cale/lms-mongo-perf

Decrease the number of queries needed for LMS courseware
This commit is contained in:
chrisndodge
2013-01-11 07:23:00 -08:00
10 changed files with 101 additions and 57 deletions

View File

@@ -42,28 +42,31 @@ def get_request_for_thread():
del frame
def get_course_by_id(course_id):
def get_course_by_id(course_id, depth=0):
"""
Given a course id, return the corresponding course descriptor.
If course_id is not valid, raises a 404.
depth: The number of levels of children for the modulestore to cache. None means infinite depth
"""
try:
course_loc = CourseDescriptor.id_to_location(course_id)
return modulestore().get_instance(course_id, course_loc)
return modulestore().get_instance(course_id, course_loc, depth=depth)
except (KeyError, ItemNotFoundError):
raise Http404("Course not found.")
def get_course_with_access(user, course_id, action):
def get_course_with_access(user, course_id, action, depth=0):
"""
Given a course_id, look up the corresponding course descriptor,
check that the user has the access to perform the specified action
on the course, and return the descriptor.
Raises a 404 if the course_id is invalid, or the user doesn't have access.
depth: The number of levels of children for the modulestore to cache. None means infinite depth
"""
course = get_course_by_id(course_id)
course = get_course_by_id(course_id, depth=depth)
if not has_access(user, course, action):
# Deliberately return a non-specific error message to avoid
# leaking info about access control settings

View File

@@ -36,8 +36,7 @@ def yield_dynamic_descriptor_descendents(descriptor, module_creator):
def get_dynamic_descriptor_children(descriptor):
if descriptor.has_dynamic_children():
module = module_creator(descriptor)
child_locations = module.get_children_locations()
return [descriptor.system.load_item(child_location) for child_location in child_locations ]
return module.get_child_descriptors()
else:
return descriptor.get_children()
@@ -291,7 +290,7 @@ def progress_summary(student, request, course, student_module_cache):
graded = section_module.metadata.get('graded', False)
scores = []
module_creator = lambda descriptor : section_module.system.get_module(descriptor.location)
module_creator = section_module.system.get_module
for module_descriptor in yield_dynamic_descriptor_descendents(section_module.descriptor, module_creator):

View File

@@ -82,7 +82,8 @@ def toc_for_course(user, request, course, active_chapter, active_section):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, user, course, depth=2)
course_module = get_module(user, request, course.location, student_module_cache, course.id)
course_module = get_module_for_descriptor(user, request, course,
student_module_cache, course.id)
if course_module is None:
return None
@@ -115,7 +116,9 @@ def toc_for_course(user, request, course, active_chapter, active_section):
return chapters
def get_module(user, request, location, student_module_cache, course_id, position=None, not_found_ok = False, wrap_xmodule_display = True):
def get_module(user, request, location, student_module_cache, course_id,
position=None, not_found_ok=False, wrap_xmodule_display=True,
depth=0):
"""
Get an instance of the xmodule class identified by location,
setting the state based on an existing StudentModule, or creating one if none
@@ -130,13 +133,19 @@ def get_module(user, request, location, student_module_cache, course_id, positio
- course_id : the course_id in the context of which to load module
- position : extra information from URL for user-specified
position within module
- depth : number of levels of descendents to cache when loading this module.
None means cache all descendents
Returns: xmodule instance, or None if the user does not have access to the
module. If there's an error, will try to return an instance of ErrorModule
if possible. If not possible, return None.
"""
try:
return _get_module(user, request, location, student_module_cache, course_id, position, wrap_xmodule_display)
location = Location(location)
descriptor = modulestore().get_instance(course_id, location, depth=depth)
return get_module_for_descriptor(user, request, descriptor, student_module_cache, course_id,
position=position, not_found_ok=not_found_ok,
wrap_xmodule_display=wrap_xmodule_display)
except ItemNotFoundError:
if not not_found_ok:
log.exception("Error in get_module")
@@ -146,12 +155,20 @@ def get_module(user, request, location, student_module_cache, course_id, positio
log.exception("Error in get_module")
return None
def _get_module(user, request, location, student_module_cache, course_id, position=None, wrap_xmodule_display = True):
def get_module_for_descriptor(user, request, descriptor, student_module_cache, course_id,
position=None, not_found_ok=False, wrap_xmodule_display=True):
"""
Actually implement get_module. See docstring there for details.
"""
return _get_module(user, request, descriptor, student_module_cache, course_id,
position=position, wrap_xmodule_display=wrap_xmodule_display)
def _get_module(user, request, descriptor, student_module_cache, course_id,
position=None, wrap_xmodule_display=True):
"""
Actually implement get_module. See docstring there for details.
"""
location = Location(location)
descriptor = modulestore().get_instance(course_id, location)
# Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load', course_id):
@@ -206,12 +223,12 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
def inner_get_module(location):
def inner_get_module(descriptor):
"""
Delegate to get_module. It does an access check, so may return None
"""
return get_module(user, request, location,
student_module_cache, course_id, position)
return get_module_for_descriptor(user, request, descriptor,
student_module_cache, course_id, position)
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
@@ -246,7 +263,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
# make an ErrorDescriptor -- assuming that the descriptor's system is ok
import_system = descriptor.system
if has_access(user, location, 'staff', course_id):
if has_access(user, descriptor.location, 'staff', course_id):
err_descriptor = ErrorDescriptor.from_xml(str(descriptor), import_system,
error_msg=exc_info_to_str(sys.exc_info()))
else:

View File

@@ -20,7 +20,7 @@ from courseware.access import has_access
from courseware.courses import (get_courses, get_course_with_access, get_courses_by_university)
import courseware.tabs as tabs
from courseware.models import StudentModuleCache
from module_render import toc_for_course, get_module, get_instance_module
from module_render import toc_for_course, get_module, get_instance_module, get_module_for_descriptor
from django_comment_client.utils import get_discussion_title
@@ -180,7 +180,7 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse
"""
course = get_course_with_access(request.user, course_id, 'load')
course = get_course_with_access(request.user, course_id, 'load', depth=2)
staff_access = has_access(request.user, course, 'staff')
registered = registered_for_course(course, request.user)
if not registered:
@@ -195,7 +195,8 @@ def index(request, course_id, chapter=None, section=None,
# Has this student been in this course before?
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)
# Load the module for the course
course_module = get_module_for_descriptor(request.user, request, course, student_module_cache, course.id)
if course_module is None:
log.warning('If you see this, something went wrong: if we got this'
' far, should have gotten a course module for this user')
@@ -215,30 +216,28 @@ def index(request, course_id, chapter=None, section=None,
'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa')
}
chapter_descriptor = course.get_child_by_url_name(chapter)
chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter)
if chapter_descriptor is not None:
instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache)
save_child_position(course_module, chapter, instance_module)
else:
raise Http404
chapter_module = get_module(request.user, request, chapter_descriptor.location,
student_module_cache, course_id)
chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter)
if chapter_module is None:
# User may be trying to access a chapter that isn't live yet
raise Http404
if section is not None:
section_descriptor = chapter_descriptor.get_child_by_url_name(section)
section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section)
if section_descriptor is None:
# Specifically asked-for section doesn't exist
raise Http404
section_student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, section_descriptor)
section_module = get_module(request.user, request,
section_descriptor.location,
section_student_module_cache, course_id, position)
# Load all descendents of the section, because we're going to display it's
# html, which in general will need all of its children
section_module = get_module(request.user, request, section_descriptor.location,
student_module_cache, course.id, depth=None)
if section_module is None:
# User may be trying to be clever and access something
# they don't have access to.