Commit ba42c022 by Bridger Maxwell

Moved the generation of the 'grading context' to a lazy property in course descriptor.

parent 2348f71c
def lazyproperty(fn):
"""
Use this decorator for lazy generation of properties that
are expensive to compute. From http://stackoverflow.com/a/3013910/86828
Example:
class Test(object):
@lazyproperty
def a(self):
print 'generating "a"'
return range(5)
Interactive Session:
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
"""
attr_name = '_lazy_' + fn.__name__
@property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
\ No newline at end of file
...@@ -3,6 +3,7 @@ import time ...@@ -3,6 +3,7 @@ import time
import dateutil.parser import dateutil.parser
import logging import logging
from util.decorators import lazyproperty
from xmodule.graders import load_grading_policy from xmodule.graders import load_grading_policy
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.seq_module import SequenceDescriptor, SequenceModule
...@@ -16,9 +17,6 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -16,9 +17,6 @@ class CourseDescriptor(SequenceDescriptor):
def __init__(self, system, definition=None, **kwargs): def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs) super(CourseDescriptor, self).__init__(system, definition, **kwargs)
self._grader = None
self._grade_cutoffs = None
msg = None msg = None
try: try:
...@@ -42,29 +40,82 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -42,29 +40,82 @@ class CourseDescriptor(SequenceDescriptor):
@property @property
def grader(self): def grader(self):
self.__load_grading_policy() return self.__grading_policy['GRADER']
return self._grader
@property @property
def grade_cutoffs(self): def grade_cutoffs(self):
self.__load_grading_policy() return self.__grading_policy['GRADE_CUTOFFS']
return self._grade_cutoffs
def __load_grading_policy(self): @lazyproperty
if not self._grader or not self._grade_cutoffs: def __grading_policy(self):
policy_string = "" policy_string = ""
try:
with self.system.resources_fs.open("grading_policy.json") as grading_policy_file:
policy_string = grading_policy_file.read()
except (IOError, ResourceNotFoundError):
log.warning("Unable to load course settings file from grading_policy.json in course " + self.id)
try: grading_policy = load_grading_policy(policy_string)
with self.system.resources_fs.open("grading_policy.json") as grading_policy_file:
policy_string = grading_policy_file.read() return grading_policy
except (IOError, ResourceNotFoundError):
log.warning("Unable to load course settings file from grading_policy.json in course " + self.id)
@lazyproperty
def grading_context(self):
"""
This returns a dictionary with keys necessary for quickly grading
a student. They are used by grades.grade()
The grading context has two keys:
graded_sections - This contains the sections that are graded, as
well as all possible children modules that can effect the
grading. This allows some sections to be skipped if the student
hasn't seen any part of it.
grading_policy = load_grading_policy(policy_string) The format is a dictionary keyed by section-type. The values are
arrays of dictionaries containing
"section_descriptor" : The section descriptor
"xmoduledescriptors" : An array of xmoduledescriptors that
could possibly be in the section, for any student
all_descriptors - This contains a list of all xmodules that can
effect grading a student. This is used to efficiently fetch
all the xmodule state for a StudentModuleCache without walking
the descriptor tree again.
self._grader = grading_policy['GRADER']
self._grade_cutoffs = grading_policy['GRADE_CUTOFFS'] """
all_descriptors = []
graded_sections = {}
def yield_descriptor_descendents(module_descriptor):
for child in module_descriptor.get_children():
yield child
for module_descriptor in yield_descriptor_descendents(child):
yield module_descriptor
for c in self.get_children():
sections = []
for s in c.get_children():
if s.metadata.get('graded', False):
xmoduledescriptors = []
for module in yield_descriptor_descendents(s):
# TODO: Only include modules that have a score here
xmoduledescriptors.append(module)
section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : xmoduledescriptors}
section_format = s.metadata.get('format', "")
graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description]
all_descriptors.extend(xmoduledescriptors)
all_descriptors.append(s)
return { 'graded_sections' : graded_sections,
'all_descriptors' : all_descriptors,}
@staticmethod @staticmethod
......
...@@ -3,6 +3,7 @@ import logging ...@@ -3,6 +3,7 @@ import logging
from django.conf import settings from django.conf import settings
from models import StudentModuleCache
from module_render import get_module, get_instance_module from module_render import get_module, get_instance_module
from xmodule import graders from xmodule import graders
from xmodule.graders import Score from xmodule.graders import Score
...@@ -10,54 +11,28 @@ from models import StudentModule ...@@ -10,54 +11,28 @@ from models import StudentModule
_log = logging.getLogger("mitx.courseware") _log = logging.getLogger("mitx.courseware")
def get_graded_sections(course_descriptor):
"""
Arguments:
course_descriptor: a CourseDescriptor object for the course to be graded
Returns:
A dictionary keyed by section-type. The values are arrays of dictionaries containing
"section_descriptor" : The section descriptor
"xmoduledescriptors" : An array of xmoduledescriptors that could possibly be in the section, for any student
"""
all_descriptors = []
graded_sections = {}
for c in course_descriptor.get_children():
sections = []
for s in c.get_children():
if s.metadata.get('graded', False):
xmoduledescriptors = []
for module in yield_descriptor_descendents(s):
# TODO: Only include modules that have a score here
xmoduledescriptors.append(module)
section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : xmoduledescriptors}
section_format = s.metadata.get('format', "")
graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description]
all_descriptors.extend(xmoduledescriptors)
all_descriptors.append(s)
return graded_sections, all_descriptors
def yield_descriptor_descendents(module_descriptor):
for child in module_descriptor.get_children():
yield child
for module_descriptor in yield_descriptor_descendents(child):
yield module_descriptor
def yield_module_descendents(module): def yield_module_descendents(module):
for child in module.get_display_items(): for child in module.get_display_items():
yield child yield child
for module in yield_module_descendents(child): for module in yield_module_descendents(child):
yield module yield module
def fast_grade(student, request, course_graded_sections, grader, student_module_cache): def grade(student, request, course, student_module_cache=None):
"""
This grades a student as quickly as possible. It reutns the
output from the course grader. More information on the format
is in the docstring for CourseGrader.
"""
grading_context = course.grading_context
if student_module_cache == None:
student_module_cache = StudentModuleCache(student, descriptors=grading_context['all_descriptors'])
totaled_scores = {} totaled_scores = {}
for section_format, sections in course_graded_sections.iteritems(): # This next complicated loop is just to collect the totaled_scores, which is
# passed to the grader
for section_format, sections in grading_context['graded_sections'].iteritems():
format_scores = [] format_scores = []
for section in sections: for section in sections:
section_descriptor = section['section_descriptor'] section_descriptor = section['section_descriptor']
...@@ -72,6 +47,8 @@ def fast_grade(student, request, course_graded_sections, grader, student_module_ ...@@ -72,6 +47,8 @@ def fast_grade(student, request, course_graded_sections, grader, student_module_
if should_grade_section: if should_grade_section:
scores = [] scores = []
# TODO: We need the request to pass into here. If we could forgo that, our arguments
# would be simpler
section_module = get_module(student, request, section_descriptor.location, student_module_cache) section_module = get_module(student, request, section_descriptor.location, student_module_cache)
# TODO: We may be able to speed this up by only getting a list of children IDs from section_module # TODO: We may be able to speed this up by only getting a list of children IDs from section_module
...@@ -106,11 +83,11 @@ def fast_grade(student, request, course_graded_sections, grader, student_module_ ...@@ -106,11 +83,11 @@ def fast_grade(student, request, course_graded_sections, grader, student_module_
totaled_scores[section_format] = format_scores totaled_scores[section_format] = format_scores
grade_summary = grade_summary = grader.grade(totaled_scores) grade_summary = course.grader.grade(totaled_scores)
return grade_summary return grade_summary
def grade_sheet(student, course, grader, student_module_cache): def progress_summary(student, course, grader, student_module_cache):
""" """
This pulls a summary of all problems in the course. This pulls a summary of all problems in the course.
......
...@@ -77,21 +77,17 @@ def gradebook(request, course_id): ...@@ -77,21 +77,17 @@ def gradebook(request, course_id):
if 'course_admin' not in user_groups(request.user): if 'course_admin' not in user_groups(request.user):
raise Http404 raise Http404
course = check_course(course_id) course = check_course(course_id)
sections, all_descriptors = grades.get_graded_sections(course)
student_objects = User.objects.all()[:100] student_objects = User.objects.all()[:100]
student_info = [] student_info = []
#TODO: Only select students who are in the course #TODO: Only select students who are in the course
for student in student_objects: for student in student_objects:
student_module_cache = StudentModuleCache(student, descriptors=all_descriptors)
student_info.append({ student_info.append({
'username': student.username, 'username': student.username,
'id': student.id, 'id': student.id,
'email': student.email, 'email': student.email,
'grade_summary': grades.fast_grade(student, request, sections, course.grader, student_module_cache), 'grade_summary': grades.grade(student, request, course),
'realname': UserProfile.objects.get(user=student).name 'realname': UserProfile.objects.get(user=student).name
}) })
...@@ -117,9 +113,8 @@ def profile(request, course_id, student_id=None): ...@@ -117,9 +113,8 @@ def profile(request, course_id, student_id=None):
student_module_cache = StudentModuleCache(request.user, course) student_module_cache = StudentModuleCache(request.user, course)
course_module = get_module(request.user, request, course.location, student_module_cache) course_module = get_module(request.user, request, course.location, student_module_cache)
courseware_summary = grades.grade_sheet(student, course_module, course.grader, student_module_cache) courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache)
sections, _ = grades.get_graded_sections(course) grade_summary = grades.grade(request.user, request, course, student_module_cache)
grade_summary = grades.fast_grade(request.user, request, sections, course.grader, student_module_cache)
context = {'name': user_info.name, context = {'name': user_info.name,
'username': student.username, 'username': student.username,
......
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