Files
edx-platform/common/lib/xmodule/xmodule/course_module.py

164 lines
5.6 KiB
Python

from fs.errors import ResourceNotFoundError
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
log = logging.getLogger(__name__)
class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
metadata_attributes = SequenceDescriptor.metadata_attributes + ('org', 'course')
def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
msg = None
try:
self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M")
except KeyError:
self.start = time.gmtime(0) #The epoch
msg = "Course loaded without a start date. id = %s" % self.id
log.critical(msg)
except ValueError as e:
self.start = time.gmtime(0) #The epoch
msg = "Course loaded with a bad start date. %s '%s'" % (self.id, e)
log.critical(msg)
# Don't call the tracker from the exception handler.
if msg is not None:
system.error_tracker(msg)
def has_started(self):
return time.gmtime() > self.start
@property
def grader(self):
return self.__grading_policy['GRADER']
@property
def grade_cutoffs(self):
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
@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 affect the
grading. This allows some sections to be skipped if the student
hasn't seen any part of it.
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.
"""
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):
# TODO: Only include modules that have a score here
xmoduledescriptors = [s for s in yield_descriptor_descendents(s)]
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
def id_to_location(course_id):
'''Convert the given course_id (org/course/name) to a location object.
Throws ValueError if course_id is of the wrong format.
'''
org, course, name = course_id.split('/')
return Location('i4x', org, course, 'course', name)
@staticmethod
def location_to_id(location):
'''Convert a location of a course to a course_id. If location category
is not "course", raise a ValueError.
location: something that can be passed to Location
'''
loc = Location(location)
if loc.category != "course":
raise ValueError("{0} is not a course location".format(loc))
return "/".join([loc.org, loc.course, loc.name])
@property
def id(self):
return self.location_to_id(self.location)
@property
def start_date_text(self):
return time.strftime("%b %d, %Y", self.start)
@property
def title(self):
return self.metadata['display_name']
@property
def number(self):
return self.location.course
@property
def wiki_namespace(self):
return self.location.course
@property
def org(self):
return self.location.org