Commit 2e7f3bc6 by Calen Pennington

Merge pull request #364 from MITx/MITx/feature/bridger/fast_course_grading

Even Faster Course Grading
parents 267d0ebc b591d43d
...@@ -31,8 +31,8 @@ class ABTestModule(XModule): ...@@ -31,8 +31,8 @@ class ABTestModule(XModule):
Implements an A/B test with an aribtrary number of competing groups Implements an A/B test with an aribtrary number of competing groups
""" """
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
if shared_state is None: if shared_state is None:
......
...@@ -11,13 +11,13 @@ from datetime import timedelta ...@@ -11,13 +11,13 @@ from datetime import timedelta
from lxml import etree from lxml import etree
from pkg_resources import resource_string from pkg_resources import resource_string
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError
from progress import Progress
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError from capa.responsetypes import StudentInputError
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from progress import Progress
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -80,9 +80,9 @@ class CapaModule(XModule): ...@@ -80,9 +80,9 @@ class CapaModule(XModule):
js_module_name = "Problem" js_module_name = "Problem"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]} css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
def __init__(self, system, location, definition, instance_state=None, def __init__(self, system, location, definition, descriptor, instance_state=None,
shared_state=None, **kwargs): shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, XModule.__init__(self, system, location, definition, descriptor, instance_state,
shared_state, **kwargs) shared_state, **kwargs)
self.attempts = 0 self.attempts = 0
...@@ -134,12 +134,14 @@ class CapaModule(XModule): ...@@ -134,12 +134,14 @@ class CapaModule(XModule):
if self.rerandomize == 'never': if self.rerandomize == 'never':
seed = 1 seed = 1
elif self.rerandomize == "per_student" and hasattr(system, 'id'): elif self.rerandomize == "per_student" and hasattr(self.system, 'id'):
seed = system.id seed = system.id
else: else:
seed = None seed = None
try: try:
# TODO (vshnayder): move as much as possible of this work and error
# checking to descriptor load time
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
instance_state, seed=seed, system=self.system) instance_state, seed=seed, system=self.system)
except Exception as err: except Exception as err:
...@@ -426,7 +428,7 @@ class CapaModule(XModule): ...@@ -426,7 +428,7 @@ class CapaModule(XModule):
event_info = dict() event_info = dict()
event_info['state'] = self.lcp.get_state() event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url() event_info['problem_id'] = self.location.url()
answers = self.make_dict_of_responses(get) answers = self.make_dict_of_responses(get)
event_info['answers'] = convert_files_to_filenames(answers) event_info['answers'] = convert_files_to_filenames(answers)
...@@ -563,6 +565,9 @@ class CapaDescriptor(RawDescriptor): ...@@ -563,6 +565,9 @@ class CapaDescriptor(RawDescriptor):
module_class = CapaModule module_class = CapaModule
stores_state = True
has_score = True
# Capa modules have some additional metadata: # Capa modules have some additional metadata:
# TODO (vshnayder): do problems have any other metadata? Do they # TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points? # actually use type and points?
......
...@@ -99,10 +99,10 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -99,10 +99,10 @@ class CourseDescriptor(SequenceDescriptor):
sections = [] sections = []
for s in c.get_children(): for s in c.get_children():
if s.metadata.get('graded', False): if s.metadata.get('graded', False):
# TODO: Only include modules that have a score here xmoduledescriptors = list(yield_descriptor_descendents(s))
xmoduledescriptors = [child for child in yield_descriptor_descendents(s)]
# The xmoduledescriptors included here are only the ones that have scores.
section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : xmoduledescriptors} section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : filter(lambda child: child.has_score, xmoduledescriptors) }
section_format = s.metadata.get('format', "") section_format = s.metadata.get('format', "")
graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description] graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description]
......
...@@ -18,9 +18,9 @@ class HtmlModule(XModule): ...@@ -18,9 +18,9 @@ class HtmlModule(XModule):
def get_html(self): def get_html(self):
return self.html return self.html
def __init__(self, system, location, definition, def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs) instance_state, shared_state, **kwargs)
self.html = self.definition['data'] self.html = self.definition['data']
......
...@@ -25,9 +25,9 @@ class SequenceModule(XModule): ...@@ -25,9 +25,9 @@ class SequenceModule(XModule):
css = {'scss': [resource_string(__name__, 'css/sequence/display.scss')]} css = {'scss': [resource_string(__name__, 'css/sequence/display.scss')]}
js_module_name = "Sequence" js_module_name = "Sequence"
def __init__(self, system, location, definition, instance_state=None, def __init__(self, system, location, definition, descriptor, instance_state=None,
shared_state=None, **kwargs): shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs) instance_state, shared_state, **kwargs)
self.position = 1 self.position = 1
...@@ -107,6 +107,8 @@ class SequenceModule(XModule): ...@@ -107,6 +107,8 @@ class SequenceModule(XModule):
class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
mako_template = 'widgets/sequence-edit.html' mako_template = 'widgets/sequence-edit.html'
module_class = SequenceModule module_class = SequenceModule
stores_state = True # For remembering where in the sequence the student is
@classmethod @classmethod
def definition_from_xml(cls, xml_object, system): def definition_from_xml(cls, xml_object, system):
......
...@@ -26,12 +26,26 @@ class CustomTagModule(XModule): ...@@ -26,12 +26,26 @@ class CustomTagModule(XModule):
More information given in <a href="/book/234">the text</a> More information given in <a href="/book/234">the text</a>
""" """
def __init__(self, system, location, definition, def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs) instance_state, shared_state, **kwargs)
self.html = definition['html']
xmltree = etree.fromstring(self.definition['data']) def get_html(self):
return self.html
class CustomTagDescriptor(RawDescriptor):
""" Descriptor for custom tags. Loads the template when created."""
module_class = CustomTagModule
@classmethod
def definition_from_xml(cls, xml_object, system):
definition = RawDescriptor.definition_from_xml(xml_object, system)
# Render the template and save it.
xmltree = etree.fromstring(definition['data'])
if 'impl' in xmltree.attrib: if 'impl' in xmltree.attrib:
template_name = xmltree.attrib['impl'] template_name = xmltree.attrib['impl']
else: else:
...@@ -45,13 +59,8 @@ class CustomTagModule(XModule): ...@@ -45,13 +59,8 @@ class CustomTagModule(XModule):
.format(location)) .format(location))
params = dict(xmltree.items()) params = dict(xmltree.items())
with self.system.filestore.open( with system.resources_fs.open('custom_tags/{name}'
'custom_tags/{name}'.format(name=template_name)) as template: .format(name=template_name)) as template:
self.html = Template(template.read()).render(**params) definition['html'] = Template(template.read()).render(**params)
def get_html(self):
return self.html
return definition
class CustomTagDescriptor(RawDescriptor):
module_class = CustomTagModule
...@@ -708,6 +708,6 @@ class ModuleProgressTest(unittest.TestCase): ...@@ -708,6 +708,6 @@ class ModuleProgressTest(unittest.TestCase):
''' '''
def test_xmodule_default(self): def test_xmodule_default(self):
'''Make sure default get_progress exists, returns None''' '''Make sure default get_progress exists, returns None'''
xm = x_module.XModule(i4xs, 'a://b/c/d/e', {}) xm = x_module.XModule(i4xs, 'a://b/c/d/e', None, {})
p = xm.get_progress() p = xm.get_progress()
self.assertEqual(p, None) self.assertEqual(p, None)
...@@ -10,8 +10,8 @@ class_priority = ['video', 'problem'] ...@@ -10,8 +10,8 @@ class_priority = ['video', 'problem']
class VerticalModule(XModule): class VerticalModule(XModule):
''' Layout module for laying out submodules vertically.''' ''' Layout module for laying out submodules vertically.'''
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
self.contents = None self.contents = None
def get_html(self): def get_html(self):
......
...@@ -23,9 +23,9 @@ class VideoModule(XModule): ...@@ -23,9 +23,9 @@ class VideoModule(XModule):
css = {'scss': [resource_string(__name__, 'css/video/display.scss')]} css = {'scss': [resource_string(__name__, 'css/video/display.scss')]}
js_module_name = "Video" js_module_name = "Video"
def __init__(self, system, location, definition, def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs) instance_state, shared_state, **kwargs)
xmltree = etree.fromstring(self.definition['data']) xmltree = etree.fromstring(self.definition['data'])
self.youtube = xmltree.get('youtube') self.youtube = xmltree.get('youtube')
...@@ -80,3 +80,5 @@ class VideoModule(XModule): ...@@ -80,3 +80,5 @@ class VideoModule(XModule):
class VideoDescriptor(RawDescriptor): class VideoDescriptor(RawDescriptor):
module_class = VideoModule module_class = VideoModule
stores_state = True
...@@ -143,7 +143,7 @@ class XModule(HTMLSnippet): ...@@ -143,7 +143,7 @@ class XModule(HTMLSnippet):
# in the module # in the module
icon_class = 'other' icon_class = 'other'
def __init__(self, system, location, definition, def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
''' '''
Construct a new xmodule Construct a new xmodule
...@@ -189,6 +189,7 @@ class XModule(HTMLSnippet): ...@@ -189,6 +189,7 @@ class XModule(HTMLSnippet):
self.system = system self.system = system
self.location = Location(location) self.location = Location(location)
self.definition = definition self.definition = definition
self.descriptor = descriptor
self.instance_state = instance_state self.instance_state = instance_state
self.shared_state = shared_state self.shared_state = shared_state
self.id = self.location.url() self.id = self.location.url()
...@@ -303,6 +304,13 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -303,6 +304,13 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
""" """
entry_point = "xmodule.v1" entry_point = "xmodule.v1"
module_class = XModule module_class = XModule
# Attributes for inpsection of the descriptor
stores_state = False # Indicates whether the xmodule state should be
# stored in a database (independent of shared state)
has_score = False # This indicates whether the xmodule is a problem-type.
# It should respond to max_score() and grade(). It can be graded or ungraded
# (like a practice problem).
# A list of metadata that this module can inherit from its parent module # A list of metadata that this module can inherit from its parent module
inheritable_metadata = ( inheritable_metadata = (
...@@ -427,6 +435,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -427,6 +435,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
system, system,
self.location, self.location,
self.definition, self.definition,
self,
metadata=self.metadata metadata=self.metadata
) )
......
...@@ -12,10 +12,12 @@ from models import StudentModule ...@@ -12,10 +12,12 @@ from models import StudentModule
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
def yield_module_descendents(module): def yield_module_descendents(module):
for child in module.get_display_items(): stack = module.get_display_items()
yield child
for module in yield_module_descendents(child): while len(stack) > 0:
yield module next_module = stack.pop()
stack.extend( next_module.get_display_items() )
yield next_module
def grade(student, request, course, student_module_cache=None): def grade(student, request, course, student_module_cache=None):
""" """
...@@ -89,7 +91,7 @@ def grade(student, request, course, student_module_cache=None): ...@@ -89,7 +91,7 @@ def grade(student, request, course, student_module_cache=None):
if graded_total.possible > 0: if graded_total.possible > 0:
format_scores.append(graded_total) format_scores.append(graded_total)
else: else:
log.exception("Unable to grade a section with a total possible score of zero. " + str(section_descriptor.id)) log.exception("Unable to grade a section with a total possible score of zero. " + str(section_descriptor.location))
totaled_scores[section_format] = format_scores totaled_scores[section_format] = format_scores
...@@ -183,6 +185,10 @@ def get_score(user, problem, student_module_cache): ...@@ -183,6 +185,10 @@ def get_score(user, problem, student_module_cache):
problem: an XModule problem: an XModule
cache: A StudentModuleCache cache: A StudentModuleCache
""" """
if not (problem.descriptor.stores_state and problem.descriptor.has_score):
# These are not problems, and do not have a score
return (None, None)
correct = 0.0 correct = 0.0
# If the ID is not in the cache, add the item # If the ID is not in the cache, add the item
......
...@@ -69,10 +69,10 @@ class StudentModuleCache(object): ...@@ -69,10 +69,10 @@ class StudentModuleCache(object):
""" """
def __init__(self, user, descriptors): def __init__(self, user, descriptors):
''' '''
Find any StudentModule objects that are needed by any child modules of the Find any StudentModule objects that are needed by any descriptor
supplied descriptor, or caches only the StudentModule objects specifically in descriptors. Avoids making multiple queries to the database.
for every descriptor in descriptors. Avoids making multiple queries to the Note: Only modules that have store_state = True or have shared
database. state will have a StudentModule.
Arguments Arguments
user: The user for which to fetch maching StudentModules user: The user for which to fetch maching StudentModules
...@@ -134,7 +134,8 @@ class StudentModuleCache(object): ...@@ -134,7 +134,8 @@ class StudentModuleCache(object):
''' '''
keys = [] keys = []
for descriptor in descriptors: for descriptor in descriptors:
keys.append(descriptor.location.url()) if descriptor.stores_state:
keys.append(descriptor.location.url())
shared_state_key = getattr(descriptor, 'shared_state_key', None) shared_state_key = getattr(descriptor, 'shared_state_key', None)
if shared_state_key is not None: if shared_state_key is not None:
......
...@@ -127,19 +127,18 @@ def get_module(user, request, location, student_module_cache, position=None): ...@@ -127,19 +127,18 @@ def get_module(user, request, location, student_module_cache, position=None):
descriptor = modulestore().get_item(location) descriptor = modulestore().get_item(location)
#TODO Only check the cache if this module can possibly have state #TODO Only check the cache if this module can possibly have state
instance_module = None
shared_module = None
if user.is_authenticated(): if user.is_authenticated():
instance_module = student_module_cache.lookup(descriptor.category, if descriptor.stores_state:
descriptor.location.url()) instance_module = student_module_cache.lookup(descriptor.category,
descriptor.location.url())
shared_state_key = getattr(descriptor, 'shared_state_key', None) shared_state_key = getattr(descriptor, 'shared_state_key', None)
if shared_state_key is not None: if shared_state_key is not None:
shared_module = student_module_cache.lookup(descriptor.category, shared_module = student_module_cache.lookup(descriptor.category,
shared_state_key) shared_state_key)
else:
shared_module = None
else:
instance_module = None
shared_module = None
instance_state = instance_module.state if instance_module is not None else None instance_state = instance_module.state if instance_module is not None else None
...@@ -206,6 +205,11 @@ def get_instance_module(user, module, student_module_cache): ...@@ -206,6 +205,11 @@ def get_instance_module(user, module, student_module_cache):
or None if this is an anonymous user or None if this is an anonymous user
""" """
if user.is_authenticated(): if user.is_authenticated():
if not module.descriptor.stores_state:
log.exception("Attempted to get the instance_module for a module "
+ str(module.id) + " which does not store state.")
return None
instance_module = student_module_cache.lookup(module.category, instance_module = student_module_cache.lookup(module.category,
module.location.url()) module.location.url())
......
...@@ -11,11 +11,11 @@ ...@@ -11,11 +11,11 @@
<%static:css group='course'/> <%static:css group='course'/>
<style type="text/css"> <style type="text/css">
.grade_a {color:green;} .grade_A {color:green;}
.grade_b {color:Chocolate;} .grade_B {color:Chocolate;}
.grade_c {color:DarkSlateGray;} .grade_C {color:DarkSlateGray;}
.grade_f {color:DimGray;} .grade_F {color:DimGray;}
.grade_none {color:LightGray;} .grade_None {color:LightGray;}
</style> </style>
</%block> </%block>
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
data_class = "grade_" + letter_grade data_class = "grade_" + letter_grade
%> %>
<td class="${data_class}">${ "{0:.0%}".format( percentage ) }</td> <td class="${data_class}" data-percent="${percentage}">${ "{0:.0%}".format( percentage ) }</td>
</%def> </%def>
%for student in students: %for student in students:
......
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