Line length and doc string cleanups
* no functionality changed in this commit.
This commit is contained in:
@@ -12,8 +12,8 @@ log = logging.getLogger(__name__)
|
||||
def process_includes(fn):
|
||||
"""
|
||||
Wraps a XModuleDescriptor.from_xml method, and modifies xml_data to replace
|
||||
any immediate child <include> items with the contents of the file that they are
|
||||
supposed to include
|
||||
any immediate child <include> items with the contents of the file that they
|
||||
are supposed to include
|
||||
"""
|
||||
@wraps(fn)
|
||||
def from_xml(cls, xml_data, system, org=None, course=None):
|
||||
@@ -25,15 +25,19 @@ def process_includes(fn):
|
||||
try:
|
||||
ifp = system.resources_fs.open(file)
|
||||
except Exception:
|
||||
log.exception('Error in problem xml include: %s' % (etree.tostring(next_include, pretty_print=True)))
|
||||
log.exception('Cannot find file %s in %s' % (file, dir))
|
||||
msg = 'Error in problem xml include: %s\n' % (
|
||||
etree.tostring(next_include, pretty_print=True))
|
||||
msg += 'Cannot find file %s in %s' % (file, dir)
|
||||
log.exception(msg)
|
||||
raise
|
||||
try:
|
||||
# read in and convert to XML
|
||||
incxml = etree.XML(ifp.read())
|
||||
except Exception:
|
||||
log.exception('Error in problem xml include: %s' % (etree.tostring(next_include, pretty_print=True)))
|
||||
log.exception('Cannot parse XML in %s' % (file))
|
||||
msg = 'Error in problem xml include: %s\n' % (
|
||||
etree.tostring(next_include, pretty_print=True))
|
||||
msg += 'Cannot parse XML in %s' % (file)
|
||||
log.exception(msg)
|
||||
raise
|
||||
# insert new XML into tree in place of inlcude
|
||||
parent = next_include.getparent()
|
||||
@@ -50,8 +54,8 @@ class SemanticSectionDescriptor(XModuleDescriptor):
|
||||
@process_includes
|
||||
def from_xml(cls, xml_data, system, org=None, course=None):
|
||||
"""
|
||||
Removes sections single child elements in favor of just embedding the child element
|
||||
|
||||
Removes sections with single child elements in favor of just embedding
|
||||
the child element
|
||||
"""
|
||||
xml_object = etree.fromstring(xml_data)
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ class ComplexEncoder(json.JSONEncoder):
|
||||
|
||||
class CapaModule(XModule):
|
||||
'''
|
||||
An XModule implementing LonCapa format problems, implemented by way of capa.capa_problem.LoncapaProblem
|
||||
An XModule implementing LonCapa format problems, implemented by way of
|
||||
capa.capa_problem.LoncapaProblem
|
||||
'''
|
||||
icon_class = 'problem'
|
||||
|
||||
@@ -77,8 +78,10 @@ class CapaModule(XModule):
|
||||
js_module_name = "Problem"
|
||||
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
|
||||
|
||||
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
|
||||
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
|
||||
def __init__(self, system, location, definition, instance_state=None,
|
||||
shared_state=None, **kwargs):
|
||||
XModule.__init__(self, system, location, definition, instance_state,
|
||||
shared_state, **kwargs)
|
||||
|
||||
self.attempts = 0
|
||||
self.max_attempts = None
|
||||
@@ -133,7 +136,8 @@ class CapaModule(XModule):
|
||||
seed = None
|
||||
|
||||
try:
|
||||
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), instance_state, seed=seed, system=self.system)
|
||||
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
|
||||
instance_state, seed=seed, system=self.system)
|
||||
except Exception:
|
||||
msg = 'cannot create LoncapaProblem %s' % self.location.url()
|
||||
log.exception(msg)
|
||||
@@ -141,15 +145,20 @@ class CapaModule(XModule):
|
||||
msg = '<p>%s</p>' % msg.replace('<', '<')
|
||||
msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<', '<')
|
||||
# create a dummy problem with error message instead of failing
|
||||
problem_text = '<problem><text><font color="red" size="+2">Problem %s has an error:</font>%s</text></problem>' % (self.location.url(), msg)
|
||||
self.lcp = LoncapaProblem(problem_text, self.location.html_id(), instance_state, seed=seed, system=self.system)
|
||||
problem_text = ('<problem><text><font color="red" size="+2">'
|
||||
'Problem %s has an error:</font>%s</text></problem>' %
|
||||
(self.location.url(), msg))
|
||||
self.lcp = LoncapaProblem(
|
||||
problem_text, self.location.html_id(),
|
||||
instance_state, seed=seed, system=self.system)
|
||||
else:
|
||||
raise
|
||||
|
||||
@property
|
||||
def rerandomize(self):
|
||||
"""
|
||||
Property accessor that returns self.metadata['rerandomize'] in a canonical form
|
||||
Property accessor that returns self.metadata['rerandomize'] in a
|
||||
canonical form
|
||||
"""
|
||||
rerandomize = self.metadata.get('rerandomize', 'always')
|
||||
if rerandomize in ("", "always", "true"):
|
||||
@@ -203,7 +212,10 @@ class CapaModule(XModule):
|
||||
except Exception, err:
|
||||
if self.system.DEBUG:
|
||||
log.exception(err)
|
||||
msg = '[courseware.capa.capa_module] <font size="+1" color="red">Failed to generate HTML for problem %s</font>' % (self.location.url())
|
||||
msg = (
|
||||
'[courseware.capa.capa_module] <font size="+1" color="red">'
|
||||
'Failed to generate HTML for problem %s</font>' %
|
||||
(self.location.url()))
|
||||
msg += '<p>Error:</p><p><pre>%s</pre></p>' % str(err).replace('<', '<')
|
||||
msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<', '<')
|
||||
html = msg
|
||||
@@ -215,8 +227,8 @@ class CapaModule(XModule):
|
||||
'weight': self.weight,
|
||||
}
|
||||
|
||||
# We using strings as truthy values, because the terminology of the check button
|
||||
# is context-specific.
|
||||
# We using strings as truthy values, because the terminology of the
|
||||
# check button is context-specific.
|
||||
check_button = "Grade" if self.max_attempts else "Check"
|
||||
reset_button = True
|
||||
save_button = True
|
||||
@@ -242,7 +254,8 @@ class CapaModule(XModule):
|
||||
if not self.lcp.done:
|
||||
reset_button = False
|
||||
|
||||
# We don't need a "save" button if infinite number of attempts and non-randomized
|
||||
# We don't need a "save" button if infinite number of attempts and
|
||||
# non-randomized
|
||||
if self.max_attempts is None and self.rerandomize != "always":
|
||||
save_button = False
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
try:
|
||||
self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M")
|
||||
except KeyError:
|
||||
self.start = time.gmtime(0) # The epoch
|
||||
log.critical("Course loaded without a start date. " + str(self.id))
|
||||
except ValueError, e:
|
||||
self.start = time.gmtime(0) # The epoch
|
||||
log.critical("Course loaded with a bad start date. " + str(self.id) + " '" + str(e) + "'")
|
||||
self.start = time.gmtime(0) #The epoch
|
||||
log.critical("Course loaded without a start date. %s", self.id)
|
||||
except ValueError as e:
|
||||
self.start = time.gmtime(0) #The epoch
|
||||
log.critical("Course loaded with a bad start date. %s '%s'",
|
||||
self.id, e)
|
||||
|
||||
def has_started(self):
|
||||
return time.gmtime() > self.start
|
||||
|
||||
@@ -19,7 +19,9 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
||||
|
||||
def __init__(self, system, definition=None, **kwargs):
|
||||
if getattr(system, 'render_template', None) is None:
|
||||
raise TypeError('{system} must have a render_template function in order to use a MakoDescriptor'.format(system=system))
|
||||
raise TypeError('{system} must have a render_template function'
|
||||
' in order to use a MakoDescriptor'.format(
|
||||
system=system))
|
||||
super(MakoModuleDescriptor, self).__init__(system, definition, **kwargs)
|
||||
|
||||
def get_context(self):
|
||||
@@ -29,4 +31,5 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
||||
return {'module': self}
|
||||
|
||||
def get_html(self):
|
||||
return self.system.render_template(self.mako_template, self.get_context())
|
||||
return self.system.render_template(
|
||||
self.mako_template, self.get_context())
|
||||
|
||||
@@ -45,13 +45,17 @@ class Location(_LocationBase):
|
||||
"""
|
||||
return re.sub('_+', '_', INVALID_CHARS.sub('_', value))
|
||||
|
||||
def __new__(_cls, loc_or_tag=None, org=None, course=None, category=None, name=None, revision=None):
|
||||
def __new__(_cls, loc_or_tag=None, org=None, course=None, category=None,
|
||||
name=None, revision=None):
|
||||
"""
|
||||
Create a new location that is a clone of the specifed one.
|
||||
|
||||
location - Can be any of the following types:
|
||||
string: should be of the form {tag}://{org}/{course}/{category}/{name}[/{revision}]
|
||||
string: should be of the form
|
||||
{tag}://{org}/{course}/{category}/{name}[/{revision}]
|
||||
|
||||
list: should be of the form [tag, org, course, category, name, revision]
|
||||
|
||||
dict: should be of the form {
|
||||
'tag': tag,
|
||||
'org': org,
|
||||
@@ -62,16 +66,19 @@ class Location(_LocationBase):
|
||||
}
|
||||
Location: another Location object
|
||||
|
||||
In both the dict and list forms, the revision is optional, and can be ommitted.
|
||||
In both the dict and list forms, the revision is optional, and can be
|
||||
ommitted.
|
||||
|
||||
Components must be composed of alphanumeric characters, or the characters '_', '-', and '.'
|
||||
Components must be composed of alphanumeric characters, or the
|
||||
characters '_', '-', and '.'
|
||||
|
||||
Components may be set to None, which may be interpreted by some contexts to mean
|
||||
wildcard selection
|
||||
Components may be set to None, which may be interpreted by some contexts
|
||||
to mean wildcard selection
|
||||
"""
|
||||
|
||||
|
||||
if org is None and course is None and category is None and name is None and revision is None:
|
||||
if (org is None and course is None and category is None and
|
||||
name is None and revision is None):
|
||||
location = loc_or_tag
|
||||
else:
|
||||
location = (loc_or_tag, org, course, category, name, revision)
|
||||
@@ -131,9 +138,11 @@ class Location(_LocationBase):
|
||||
|
||||
def html_id(self):
|
||||
"""
|
||||
Return a string with a version of the location that is safe for use in html id attributes
|
||||
Return a string with a version of the location that is safe for use in
|
||||
html id attributes
|
||||
"""
|
||||
return "-".join(str(v) for v in self.list() if v is not None).replace('.', '_')
|
||||
return "-".join(str(v) for v in self.list()
|
||||
if v is not None).replace('.', '_')
|
||||
|
||||
def dict(self):
|
||||
"""
|
||||
@@ -154,7 +163,8 @@ class Location(_LocationBase):
|
||||
|
||||
class ModuleStore(object):
|
||||
"""
|
||||
An abstract interface for a database backend that stores XModuleDescriptor instances
|
||||
An abstract interface for a database backend that stores XModuleDescriptor
|
||||
instances
|
||||
"""
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
@@ -164,13 +174,16 @@ class ModuleStore(object):
|
||||
|
||||
If any segment of the location is None except revision, raises
|
||||
xmodule.modulestore.exceptions.InsufficientSpecificationError
|
||||
If no object is found at that location, raises xmodule.modulestore.exceptions.ItemNotFoundError
|
||||
|
||||
If no object is found at that location, raises
|
||||
xmodule.modulestore.exceptions.ItemNotFoundError
|
||||
|
||||
location: Something that can be passed to Location
|
||||
|
||||
depth (int): An argument that some module stores may use to prefetch descendents of the queried modules
|
||||
for more efficient results later in the request. The depth is counted in the number of
|
||||
calls to get_children() to cache. None indicates to cache all descendents
|
||||
depth (int): An argument that some module stores may use to prefetch
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of calls to
|
||||
get_children() to cache. None indicates to cache all descendents
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -182,9 +195,10 @@ class ModuleStore(object):
|
||||
|
||||
location: Something that can be passed to Location
|
||||
|
||||
depth: An argument that some module stores may use to prefetch descendents of the queried modules
|
||||
for more efficient results later in the request. The depth is counted in the number of calls
|
||||
to get_children() to cache. None indicates to cache all descendents
|
||||
depth: An argument that some module stores may use to prefetch
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of calls to
|
||||
get_children() to cache. None indicates to cache all descendents
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -228,4 +242,3 @@ class ModuleStore(object):
|
||||
in this modulestore.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class RawDescriptor(MakoModuleDescriptor, XmlDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of it's data and children
|
||||
Module that provides a raw editing view of its data and children
|
||||
"""
|
||||
mako_template = "widgets/raw-edit.html"
|
||||
|
||||
|
||||
@@ -20,12 +20,15 @@ class_priority = ['video', 'problem']
|
||||
class SequenceModule(XModule):
|
||||
''' Layout module which lays out content in a temporal sequence
|
||||
'''
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/sequence/display.coffee')]}
|
||||
js = {'coffee': [resource_string(__name__,
|
||||
'js/src/sequence/display.coffee')]}
|
||||
css = {'scss': [resource_string(__name__, 'css/sequence/display.scss')]}
|
||||
js_module_name = "Sequence"
|
||||
|
||||
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
|
||||
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
|
||||
def __init__(self, system, location, definition, instance_state=None,
|
||||
shared_state=None, **kwargs):
|
||||
XModule.__init__(self, system, location, definition,
|
||||
instance_state, shared_state, **kwargs)
|
||||
self.position = 1
|
||||
|
||||
if instance_state is not None:
|
||||
@@ -92,7 +95,8 @@ class SequenceModule(XModule):
|
||||
self.rendered = True
|
||||
|
||||
def get_icon_class(self):
|
||||
child_classes = set(child.get_icon_class() for child in self.get_children())
|
||||
child_classes = set(child.get_icon_class()
|
||||
for child in self.get_children())
|
||||
new_class = 'other'
|
||||
for c in class_priority:
|
||||
if c in child_classes:
|
||||
@@ -114,5 +118,6 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
|
||||
def definition_to_xml(self, resource_fs):
|
||||
xml_object = etree.Element('sequential')
|
||||
for child in self.get_children():
|
||||
xml_object.append(etree.fromstring(child.export_to_xml(resource_fs)))
|
||||
xml_object.append(
|
||||
etree.fromstring(child.export_to_xml(resource_fs)))
|
||||
return xml_object
|
||||
|
||||
@@ -31,23 +31,28 @@ class Plugin(object):
|
||||
def load_class(cls, identifier, default=None):
|
||||
"""
|
||||
Loads a single class instance specified by identifier. If identifier
|
||||
specifies more than a single class, then logs a warning and returns the first
|
||||
class identified.
|
||||
specifies more than a single class, then logs a warning and returns the
|
||||
first class identified.
|
||||
|
||||
If default is not None, will return default if no entry_point matching identifier
|
||||
is found. Otherwise, will raise a ModuleMissingError
|
||||
If default is not None, will return default if no entry_point matching
|
||||
identifier is found. Otherwise, will raise a ModuleMissingError
|
||||
"""
|
||||
if cls._plugin_cache is None:
|
||||
cls._plugin_cache = {}
|
||||
|
||||
if identifier not in cls._plugin_cache:
|
||||
identifier = identifier.lower()
|
||||
classes = list(pkg_resources.iter_entry_points(cls.entry_point, name=identifier))
|
||||
classes = list(pkg_resources.iter_entry_points(
|
||||
cls.entry_point, name=identifier))
|
||||
|
||||
if len(classes) > 1:
|
||||
log.warning("Found multiple classes for {entry_point} with identifier {id}: {classes}. Returning the first one.".format(
|
||||
log.warning("Found multiple classes for {entry_point} with "
|
||||
"identifier {id}: {classes}. "
|
||||
"Returning the first one.".format(
|
||||
entry_point=cls.entry_point,
|
||||
id=identifier,
|
||||
classes=", ".join(class_.module_name for class_ in classes)))
|
||||
classes=", ".join(
|
||||
class_.module_name for class_ in classes)))
|
||||
|
||||
if len(classes) == 0:
|
||||
if default is not None:
|
||||
@@ -79,9 +84,12 @@ class HTMLSnippet(object):
|
||||
def get_javascript(cls):
|
||||
"""
|
||||
Return a dictionary containing some of the following keys:
|
||||
|
||||
coffee: A list of coffeescript fragments that should be compiled and
|
||||
placed on the page
|
||||
js: A list of javascript fragments that should be included on the page
|
||||
|
||||
js: A list of javascript fragments that should be included on the
|
||||
page
|
||||
|
||||
All of these will be loaded onto the page in the CMS
|
||||
"""
|
||||
@@ -91,12 +99,15 @@ class HTMLSnippet(object):
|
||||
def get_css(cls):
|
||||
"""
|
||||
Return a dictionary containing some of the following keys:
|
||||
css: A list of css fragments that should be applied to the html contents
|
||||
of the snippet
|
||||
sass: A list of sass fragments that should be applied to the html contents
|
||||
of the snippet
|
||||
scss: A list of scss fragments that should be applied to the html contents
|
||||
of the snippet
|
||||
|
||||
css: A list of css fragments that should be applied to the html
|
||||
contents of the snippet
|
||||
|
||||
sass: A list of sass fragments that should be applied to the html
|
||||
contents of the snippet
|
||||
|
||||
scss: A list of scss fragments that should be applied to the html
|
||||
contents of the snippet
|
||||
"""
|
||||
return cls.css
|
||||
|
||||
@@ -104,47 +115,70 @@ class HTMLSnippet(object):
|
||||
"""
|
||||
Return the html used to display this snippet
|
||||
"""
|
||||
raise NotImplementedError("get_html() must be provided by specific modules - not present in {0}"
|
||||
raise NotImplementedError(
|
||||
"get_html() must be provided by specific modules - not present in {0}"
|
||||
.format(self.__class__))
|
||||
|
||||
|
||||
class XModule(HTMLSnippet):
|
||||
''' Implements a generic learning module.
|
||||
|
||||
Subclasses must at a minimum provide a definition for get_html in order to be displayed to users.
|
||||
Subclasses must at a minimum provide a definition for get_html in order
|
||||
to be displayed to users.
|
||||
|
||||
See the HTML module for a simple example.
|
||||
'''
|
||||
|
||||
# The default implementation of get_icon_class returns the icon_class attribute of the class
|
||||
# This attribute can be overridden by subclasses, and the function can also be overridden
|
||||
# if the icon class depends on the data in the module
|
||||
# The default implementation of get_icon_class returns the icon_class
|
||||
# attribute of the class
|
||||
#
|
||||
# This attribute can be overridden by subclasses, and
|
||||
# the function can also be overridden if the icon class depends on the data
|
||||
# in the module
|
||||
icon_class = 'other'
|
||||
|
||||
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
|
||||
def __init__(self, system, location, definition,
|
||||
instance_state=None, shared_state=None, **kwargs):
|
||||
'''
|
||||
Construct a new xmodule
|
||||
|
||||
system: A ModuleSystem allowing access to external resources
|
||||
|
||||
location: Something Location-like that identifies this xmodule
|
||||
definition: A dictionary containing 'data' and 'children'. Both are optional
|
||||
'data': is JSON-like (string, dictionary, list, bool, or None, optionally nested).
|
||||
This defines all of the data necessary for a problem to display that is intrinsic to the problem.
|
||||
It should not include any data that would vary between two courses using the same problem
|
||||
|
||||
definition: A dictionary containing 'data' and 'children'. Both are
|
||||
optional
|
||||
|
||||
'data': is JSON-like (string, dictionary, list, bool, or None,
|
||||
optionally nested).
|
||||
|
||||
This defines all of the data necessary for a problem to display
|
||||
that is intrinsic to the problem. It should not include any
|
||||
data that would vary between two courses using the same problem
|
||||
(due dates, grading policy, randomization, etc.)
|
||||
'children': is a list of Location-like values for child modules that this module depends on
|
||||
instance_state: A string of serialized json that contains the state of this module for
|
||||
current student accessing the system, or None if no state has been saved
|
||||
shared_state: A string of serialized json that contains the state that is shared between
|
||||
this module and any modules of the same type with the same shared_state_key. This
|
||||
state is only shared per-student, not across different students
|
||||
kwargs: Optional arguments. Subclasses should always accept kwargs and pass them
|
||||
to the parent class constructor.
|
||||
|
||||
'children': is a list of Location-like values for child modules that
|
||||
this module depends on
|
||||
|
||||
instance_state: A string of serialized json that contains the state of
|
||||
this module for current student accessing the system, or None if
|
||||
no state has been saved
|
||||
|
||||
shared_state: A string of serialized json that contains the state that
|
||||
is shared between this module and any modules of the same type with
|
||||
the same shared_state_key. This state is only shared per-student,
|
||||
not across different students
|
||||
|
||||
kwargs: Optional arguments. Subclasses should always accept kwargs and
|
||||
pass them to the parent class constructor.
|
||||
|
||||
Current known uses of kwargs:
|
||||
metadata: SCAFFOLDING - This dictionary will be split into several different types of metadata
|
||||
in the future (course policy, modification history, etc).
|
||||
A dictionary containing data that specifies information that is particular
|
||||
to a problem in the context of a course
|
||||
|
||||
metadata: SCAFFOLDING - This dictionary will be split into
|
||||
several different types of metadata in the future (course
|
||||
policy, modification history, etc). A dictionary containing
|
||||
data that specifies information that is particular to a
|
||||
problem in the context of a course
|
||||
'''
|
||||
self.system = system
|
||||
self.location = Location(location)
|
||||
@@ -217,16 +251,21 @@ class XModule(HTMLSnippet):
|
||||
|
||||
def max_score(self):
|
||||
''' Maximum score. Two notes:
|
||||
* This is generic; in abstract, a problem could be 3/5 points on one randomization, and 5/7 on another
|
||||
* In practice, this is a Very Bad Idea, and (a) will break some code in place (although that code
|
||||
should get fixed), and (b) break some analytics we plan to put in place.
|
||||
|
||||
* This is generic; in abstract, a problem could be 3/5 points on one
|
||||
randomization, and 5/7 on another
|
||||
|
||||
* In practice, this is a Very Bad Idea, and (a) will break some code
|
||||
in place (although that code should get fixed), and (b) break some
|
||||
analytics we plan to put in place.
|
||||
'''
|
||||
return None
|
||||
|
||||
def get_progress(self):
|
||||
''' Return a progress.Progress object that represents how far the student has gone
|
||||
in this module. Must be implemented to get correct progress tracking behavior in
|
||||
nesting modules like sequence and vertical.
|
||||
''' Return a progress.Progress object that represents how far the
|
||||
student has gone in this module. Must be implemented to get correct
|
||||
progress tracking behavior in nesting modules like sequence and
|
||||
vertical.
|
||||
|
||||
If this module has no notion of progress, return None.
|
||||
'''
|
||||
@@ -240,13 +279,14 @@ class XModule(HTMLSnippet):
|
||||
|
||||
class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
"""
|
||||
An XModuleDescriptor is a specification for an element of a course. This could
|
||||
be a problem, an organizational element (a group of content), or a segment of video,
|
||||
for example.
|
||||
An XModuleDescriptor is a specification for an element of a course. This
|
||||
could be a problem, an organizational element (a group of content), or a
|
||||
segment of video, for example.
|
||||
|
||||
XModuleDescriptors are independent and agnostic to the current student state on a
|
||||
problem. They handle the editing interface used by instructors to create a problem,
|
||||
and can generate XModules (which do know about student state).
|
||||
XModuleDescriptors are independent and agnostic to the current student state
|
||||
on a problem. They handle the editing interface used by instructors to
|
||||
create a problem, and can generate XModules (which do know about student
|
||||
state).
|
||||
"""
|
||||
entry_point = "xmodule.v1"
|
||||
module_class = XModule
|
||||
@@ -255,46 +295,58 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
inheritable_metadata = (
|
||||
'graded', 'start', 'due', 'graceperiod', 'showanswer', 'rerandomize',
|
||||
|
||||
# This is used by the XMLModuleStore to provide for locations for static files,
|
||||
# and will need to be removed when that code is removed
|
||||
# TODO: This is used by the XMLModuleStore to provide for locations for
|
||||
# static files, and will need to be removed when that code is removed
|
||||
'data_dir'
|
||||
)
|
||||
|
||||
# A list of descriptor attributes that must be equal for the descriptors to be
|
||||
# equal
|
||||
equality_attributes = ('definition', 'metadata', 'location', 'shared_state_key', '_inherited_metadata')
|
||||
# A list of descriptor attributes that must be equal for the descriptors to
|
||||
# be equal
|
||||
equality_attributes = ('definition', 'metadata', 'location',
|
||||
'shared_state_key', '_inherited_metadata')
|
||||
|
||||
# ============================= STRUCTURAL MANIPULATION ===========================
|
||||
# ============================= STRUCTURAL MANIPULATION ===================
|
||||
def __init__(self,
|
||||
system,
|
||||
definition=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Construct a new XModuleDescriptor. The only required arguments are the
|
||||
system, used for interaction with external resources, and the definition,
|
||||
which specifies all the data needed to edit and display the problem (but none
|
||||
of the associated metadata that handles recordkeeping around the problem).
|
||||
system, used for interaction with external resources, and the
|
||||
definition, which specifies all the data needed to edit and display the
|
||||
problem (but none of the associated metadata that handles recordkeeping
|
||||
around the problem).
|
||||
|
||||
This allows for maximal flexibility to add to the interface while preserving
|
||||
backwards compatibility.
|
||||
This allows for maximal flexibility to add to the interface while
|
||||
preserving backwards compatibility.
|
||||
|
||||
system: An XModuleSystem for interacting with external resources
|
||||
definition: A dict containing `data` and `children` representing the problem definition
|
||||
system: A DescriptorSystem for interacting with external resources
|
||||
|
||||
definition: A dict containing `data` and `children` representing the
|
||||
problem definition
|
||||
|
||||
Current arguments passed in kwargs:
|
||||
location: A xmodule.modulestore.Location object indicating the name and ownership of this problem
|
||||
shared_state_key: The key to use for sharing StudentModules with other
|
||||
modules of this type
|
||||
|
||||
location: A xmodule.modulestore.Location object indicating the name
|
||||
and ownership of this problem
|
||||
|
||||
shared_state_key: The key to use for sharing StudentModules with
|
||||
other modules of this type
|
||||
|
||||
metadata: A dictionary containing the following optional keys:
|
||||
goals: A list of strings of learning goals associated with this module
|
||||
display_name: The name to use for displaying this module to the user
|
||||
goals: A list of strings of learning goals associated with this
|
||||
module
|
||||
display_name: The name to use for displaying this module to the
|
||||
user
|
||||
format: The format of this module ('Homework', 'Lab', etc)
|
||||
graded (bool): Whether this module is should be graded or not
|
||||
start (string): The date for which this module will be available
|
||||
due (string): The due date for this module
|
||||
graceperiod (string): The amount of grace period to allow when enforcing the due date
|
||||
graceperiod (string): The amount of grace period to allow when
|
||||
enforcing the due date
|
||||
showanswer (string): When to show answers for this module
|
||||
rerandomize (string): When to generate a newly randomized instance of the module data
|
||||
rerandomize (string): When to generate a newly randomized
|
||||
instance of the module data
|
||||
"""
|
||||
self.system = system
|
||||
self.metadata = kwargs.get('metadata', {})
|
||||
@@ -321,7 +373,8 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
self.metadata[attr] = metadata[attr]
|
||||
|
||||
def get_children(self):
|
||||
"""Returns a list of XModuleDescriptor instances for the children of this module"""
|
||||
"""Returns a list of XModuleDescriptor instances for the children of
|
||||
this module"""
|
||||
if self._child_instances is None:
|
||||
self._child_instances = []
|
||||
for child_loc in self.definition.get('children', []):
|
||||
@@ -333,8 +386,9 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
|
||||
def xmodule_constructor(self, system):
|
||||
"""
|
||||
Returns a constructor for an XModule. This constructor takes two arguments:
|
||||
instance_state and shared_state, and returns a fully nstantiated XModule
|
||||
Returns a constructor for an XModule. This constructor takes two
|
||||
arguments: instance_state and shared_state, and returns a fully
|
||||
instantiated XModule
|
||||
"""
|
||||
return partial(
|
||||
self.module_class,
|
||||
@@ -344,7 +398,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
metadata=self.metadata
|
||||
)
|
||||
|
||||
# ================================= JSON PARSING ===================================
|
||||
# ================================= JSON PARSING ===========================
|
||||
@staticmethod
|
||||
def load_from_json(json_data, system, default_class=None):
|
||||
"""
|
||||
@@ -366,13 +420,14 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
|
||||
Creates an instance of this descriptor from the supplied json_data.
|
||||
This may be overridden by subclasses
|
||||
|
||||
json_data: A json object specifying the definition and any optional keyword arguments for
|
||||
the XModuleDescriptor
|
||||
system: An XModuleSystem for interacting with external resources
|
||||
json_data: A json object specifying the definition and any optional
|
||||
keyword arguments for the XModuleDescriptor
|
||||
|
||||
system: A DescriptorSystem for interacting with external resources
|
||||
"""
|
||||
return cls(system=system, **json_data)
|
||||
|
||||
# ================================= XML PARSING ====================================
|
||||
# ================================= XML PARSING ============================
|
||||
@staticmethod
|
||||
def load_from_xml(xml_data,
|
||||
system,
|
||||
@@ -487,24 +542,33 @@ class ModuleSystem(object):
|
||||
'''
|
||||
def __init__(self, ajax_url, track_function,
|
||||
get_module, render_template, replace_urls,
|
||||
user=None, filestore=None, debug=False, xqueue_callback_url=None):
|
||||
user=None, filestore=None, debug=False,
|
||||
xqueue_callback_url=None):
|
||||
'''
|
||||
Create a closure around the system environment.
|
||||
|
||||
ajax_url - the url where ajax calls to the encapsulating module go.
|
||||
|
||||
track_function - function of (event_type, event), intended for logging
|
||||
or otherwise tracking the event.
|
||||
TODO: Not used, and has inconsistent args in different
|
||||
files. Update or remove.
|
||||
|
||||
get_module - function that takes (location) and returns a corresponding
|
||||
module instance object.
|
||||
render_template - a function that takes (template_file, context), and returns
|
||||
rendered html.
|
||||
user - The user to base the random number generator seed off of for this request
|
||||
filestore - A filestore ojbect. Defaults to an instance of OSFS based at
|
||||
settings.DATA_DIR.
|
||||
module instance object.
|
||||
|
||||
render_template - a function that takes (template_file, context), and
|
||||
returns rendered html.
|
||||
|
||||
user - The user to base the random number generator seed off of for this
|
||||
request
|
||||
|
||||
filestore - A filestore ojbect. Defaults to an instance of OSFS based
|
||||
at settings.DATA_DIR.
|
||||
|
||||
replace_urls - TEMPORARY - A function like static_replace.replace_urls
|
||||
that capa_module can use to fix up the static urls in ajax results.
|
||||
that capa_module can use to fix up the static urls in
|
||||
ajax results.
|
||||
'''
|
||||
self.ajax_url = ajax_url
|
||||
self.xqueue_callback_url = xqueue_callback_url
|
||||
@@ -529,4 +593,3 @@ class ModuleSystem(object):
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__dict__)
|
||||
|
||||
|
||||
@@ -13,13 +13,19 @@ log = logging.getLogger(__name__)
|
||||
# TODO (cpennington): This was implemented in an attempt to improve performance,
|
||||
# but the actual improvement wasn't measured (and it was implemented late at night).
|
||||
# We should check if it hurts, and whether there's a better way of doing lazy loading
|
||||
|
||||
class LazyLoadingDict(MutableMapping):
|
||||
"""
|
||||
A dictionary object that lazily loads it's contents from a provided
|
||||
function on reads (of members that haven't already been set)
|
||||
A dictionary object that lazily loads its contents from a provided
|
||||
function on reads (of members that haven't already been set).
|
||||
"""
|
||||
|
||||
def __init__(self, loader):
|
||||
'''
|
||||
On the first read from this dictionary, it will call loader() to
|
||||
populate its contents. loader() must return something dict-like. Any
|
||||
elements set before the first read will be preserved.
|
||||
'''
|
||||
self._contents = {}
|
||||
self._loaded = False
|
||||
self._loader = loader
|
||||
@@ -70,10 +76,12 @@ _AttrMapBase = namedtuple('_AttrMap', 'metadata_key to_metadata from_metadata')
|
||||
|
||||
class AttrMap(_AttrMapBase):
|
||||
"""
|
||||
A class that specifies a metadata_key, a function to transform an xml attribute to be placed in that key,
|
||||
and to transform that key value
|
||||
A class that specifies a metadata_key, a function to transform an xml
|
||||
attribute to be placed in that key, and to transform that key value
|
||||
"""
|
||||
def __new__(_cls, metadata_key, to_metadata=lambda x: x, from_metadata=lambda x: x):
|
||||
def __new__(_cls, metadata_key,
|
||||
to_metadata=lambda x: x,
|
||||
from_metadata=lambda x: x):
|
||||
return _AttrMapBase.__new__(_cls, metadata_key, to_metadata, from_metadata)
|
||||
|
||||
|
||||
@@ -93,7 +101,9 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
# A dictionary mapping xml attribute names to functions of the value
|
||||
# that return the metadata key and value
|
||||
xml_attribute_map = {
|
||||
'graded': AttrMap('graded', lambda val: val == 'true', lambda val: str(val).lower()),
|
||||
'graded': AttrMap('graded',
|
||||
lambda val: val == 'true',
|
||||
lambda val: str(val).lower()),
|
||||
'name': AttrMap('display_name'),
|
||||
}
|
||||
|
||||
@@ -105,12 +115,14 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
|
||||
xml_object: An etree Element
|
||||
"""
|
||||
raise NotImplementedError("%s does not implement definition_from_xml" % cls.__name__)
|
||||
raise NotImplementedError(
|
||||
"%s does not implement definition_from_xml" % cls.__name__)
|
||||
|
||||
@classmethod
|
||||
def clean_metadata_from_xml(cls, xml_object):
|
||||
"""
|
||||
Remove any attribute named in self.metadata_attributes from the supplied xml_object
|
||||
Remove any attribute named in cls.metadata_attributes from the supplied
|
||||
xml_object
|
||||
"""
|
||||
for attr in cls.metadata_attributes:
|
||||
if xml_object.get(attr) is not None:
|
||||
@@ -134,7 +146,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
|
||||
xml_data: A string of xml that will be translated into data and children for
|
||||
this module
|
||||
system: An XModuleSystem for interacting with external resources
|
||||
system: A DescriptorSystem for interacting with external resources
|
||||
org and course are optional strings that will be used in the generated modules
|
||||
url identifiers
|
||||
"""
|
||||
@@ -157,6 +169,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
else:
|
||||
filepath = cls._format_filepath(xml_object.tag, filename)
|
||||
|
||||
# VS[compat]
|
||||
# TODO (cpennington): If the file doesn't exist at the right path,
|
||||
# give the class a chance to fix it up. The file will be written out again
|
||||
# in the correct format.
|
||||
@@ -243,4 +256,5 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
"""
|
||||
Return a new etree Element object created from this modules definition.
|
||||
"""
|
||||
raise NotImplementedError("%s does not implement definition_to_xml" % self.__class__.__name__)
|
||||
raise NotImplementedError(
|
||||
"%s does not implement definition_to_xml" % self.__class__.__name__)
|
||||
|
||||
Reference in New Issue
Block a user