Moved the generation of the 'grading context' to a lazy property in course descriptor.
This commit is contained in:
36
common/djangoapps/util/decorators.py
Normal file
36
common/djangoapps/util/decorators.py
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
|
||||
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
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
import dateutil.parser
|
||||
import logging
|
||||
|
||||
from util.decorators import lazyproperty
|
||||
from xmodule.graders import load_grading_policy
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.seq_module import SequenceDescriptor, SequenceModule
|
||||
@@ -16,9 +17,6 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
|
||||
def __init__(self, system, definition=None, **kwargs):
|
||||
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
|
||||
|
||||
self._grader = None
|
||||
self._grade_cutoffs = None
|
||||
|
||||
msg = None
|
||||
try:
|
||||
@@ -42,29 +40,82 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
|
||||
@property
|
||||
def grader(self):
|
||||
self.__load_grading_policy()
|
||||
return self._grader
|
||||
return self.__grading_policy['GRADER']
|
||||
|
||||
@property
|
||||
def grade_cutoffs(self):
|
||||
self.__load_grading_policy()
|
||||
return self._grade_cutoffs
|
||||
return self.__grading_policy['GRADE_CUTOFFS']
|
||||
|
||||
@lazyproperty
|
||||
def __grading_policy(self):
|
||||
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)
|
||||
|
||||
grading_policy = load_grading_policy(policy_string)
|
||||
|
||||
return grading_policy
|
||||
|
||||
|
||||
def __load_grading_policy(self):
|
||||
if not self._grader or not self._grade_cutoffs:
|
||||
policy_string = ""
|
||||
@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.
|
||||
|
||||
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)
|
||||
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.
|
||||
|
||||
grading_policy = load_grading_policy(policy_string)
|
||||
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from models import StudentModuleCache
|
||||
from module_render import get_module, get_instance_module
|
||||
from xmodule import graders
|
||||
from xmodule.graders import Score
|
||||
@@ -10,54 +11,28 @@ from models import StudentModule
|
||||
|
||||
_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):
|
||||
for child in module.get_display_items():
|
||||
yield child
|
||||
for module in yield_module_descendents(child):
|
||||
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 = {}
|
||||
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 = []
|
||||
for section in sections:
|
||||
section_descriptor = section['section_descriptor']
|
||||
@@ -72,6 +47,8 @@ def fast_grade(student, request, course_graded_sections, grader, student_module_
|
||||
|
||||
if should_grade_section:
|
||||
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)
|
||||
|
||||
# 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_
|
||||
|
||||
totaled_scores[section_format] = format_scores
|
||||
|
||||
grade_summary = grade_summary = grader.grade(totaled_scores)
|
||||
grade_summary = course.grader.grade(totaled_scores)
|
||||
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.
|
||||
|
||||
|
||||
@@ -77,21 +77,17 @@ def gradebook(request, course_id):
|
||||
if 'course_admin' not in user_groups(request.user):
|
||||
raise Http404
|
||||
course = check_course(course_id)
|
||||
|
||||
sections, all_descriptors = grades.get_graded_sections(course)
|
||||
|
||||
|
||||
student_objects = User.objects.all()[:100]
|
||||
student_info = []
|
||||
|
||||
#TODO: Only select students who are in the course
|
||||
for student in student_objects:
|
||||
student_module_cache = StudentModuleCache(student, descriptors=all_descriptors)
|
||||
|
||||
for student in student_objects:
|
||||
student_info.append({
|
||||
'username': student.username,
|
||||
'id': student.id,
|
||||
'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
|
||||
})
|
||||
|
||||
@@ -117,9 +113,8 @@ def profile(request, course_id, student_id=None):
|
||||
student_module_cache = StudentModuleCache(request.user, course)
|
||||
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)
|
||||
sections, _ = grades.get_graded_sections(course)
|
||||
grade_summary = grades.fast_grade(request.user, request, sections, course.grader, student_module_cache)
|
||||
courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache)
|
||||
grade_summary = grades.grade(request.user, request, course, student_module_cache)
|
||||
|
||||
context = {'name': user_info.name,
|
||||
'username': student.username,
|
||||
|
||||
Reference in New Issue
Block a user