Commit 14431624 by Calen Pennington

Remove old parts of xmodules used in sketch

parent 0a4dcf1e
......@@ -8,7 +8,6 @@ import hashlib
from .util.decorators import lazyproperty
from .graders import load_grading_policy
from .modulestore import Location
from .seq_module import SequenceDescriptor, SequenceModule
from .timeparse import parse_time, stringify_time
from .structure_module import StructureModule
from .xmodule import Plugin
......@@ -164,302 +163,3 @@ class Reschedule(Policy):
class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
class Textbook:
def __init__(self, title, book_url):
self.title = title
self.book_url = book_url
self.table_of_contents = self._get_toc_from_s3()
self.start_page = int(self.table_of_contents[0].attrib['page'])
# The last page should be the last element in the table of contents,
# but it may be nested. So recurse all the way down the last element
last_el = self.table_of_contents[-1]
while last_el.getchildren():
last_el = last_el[-1]
self.end_page = int(last_el.attrib['page'])
@property
def table_of_contents(self):
return self.table_of_contents
def _get_toc_from_s3(self):
"""
Accesses the textbook's table of contents (default name "toc.xml") at the URL self.book_url
Returns XML tree representation of the table of contents
"""
toc_url = self.book_url + 'toc.xml'
# Get the table of contents from S3
log.info("Retrieving textbook table of contents from %s" % toc_url)
try:
r = requests.get(toc_url)
except Exception as err:
msg = 'Error %s: Unable to retrieve textbook table of contents at %s' % (err, toc_url)
log.error(msg)
raise Exception(msg)
# TOC is XML. Parse it
try:
table_of_contents = etree.fromstring(r.text)
except Exception as err:
msg = 'Error %s: Unable to parse XML for textbook table of contents at %s' % (err, toc_url)
log.error(msg)
raise Exception(msg)
return table_of_contents
def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
self.textbooks = []
for title, book_url in self.definition['data']['textbooks']:
try:
self.textbooks.append(self.Textbook(title, book_url))
except:
# If we can't get to S3 (e.g. on a train with no internet), don't break
# the rest of the courseware.
log.exception("Couldn't load textbook ({0}, {1})".format(title, book_url))
continue
self.wiki_slug = self.definition['data']['wiki_slug'] or self.location.course
msg = None
if self.start is None:
msg = "Course loaded without a valid start date. id = %s" % self.id
# hack it -- start in 1970
self.metadata['start'] = stringify_time(time.gmtime(0))
log.critical(msg)
system.error_tracker(msg)
self.enrollment_start = self._try_parse_time("enrollment_start")
self.enrollment_end = self._try_parse_time("enrollment_end")
self.end = self._try_parse_time("end")
# NOTE: relies on the modulestore to call set_grading_policy() right after
# init. (Modulestore is in charge of figuring out where to load the policy from)
# NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
# disable the syllabus content for courses that do not provide a syllabus
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
def set_grading_policy(self, policy_str):
"""Parse the policy specified in policy_str, and save it"""
try:
self._grading_policy = load_grading_policy(policy_str)
except:
self.system.error_tracker("Failed to load grading policy")
# Setting this to an empty dictionary will lead to errors when
# grading needs to happen, but should allow course staff to see
# the error log.
self._grading_policy = {}
@classmethod
def definition_from_xml(cls, xml_object, system):
textbooks = []
for textbook in xml_object.findall("textbook"):
textbooks.append((textbook.get('title'), textbook.get('book_url')))
xml_object.remove(textbook)
#Load the wiki tag if it exists
wiki_slug = None
wiki_tag = xml_object.find("wiki")
if wiki_tag is not None:
wiki_slug = wiki_tag.attrib.get("slug", default=None)
xml_object.remove(wiki_tag)
definition = super(CourseDescriptor, cls).definition_from_xml(xml_object, system)
definition.setdefault('data', {})['textbooks'] = textbooks
definition['data']['wiki_slug'] = wiki_slug
return definition
def has_ended(self):
"""
Returns True if the current time is after the specified course end date.
Returns False if there is no end date specified.
"""
if self.end_date is None:
return False
return time.gmtime() > self.end
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']
@property
def tabs(self):
"""
Return the tabs config, as a python object, or None if not specified.
"""
return self.metadata.get('tabs')
@property
def show_calculator(self):
return self.metadata.get("show_calculator", None) == "Yes"
@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):
xmoduledescriptors = list(yield_descriptor_descendents(s))
xmoduledescriptors.append(s)
# The xmoduledescriptors included here are only the ones that have scores.
section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : filter(lambda child: child.has_score, 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 make_id(org, course, url_name):
return '/'.join([org, course, url_name])
@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 the course_id for this course"""
return self.location_to_id(self.location)
@property
def start_date_text(self):
displayed_start = self._try_parse_time('advertised_start') or self.start
return time.strftime("%b %d, %Y", displayed_start)
# An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows
# courses to share the same css_class across runs even if they have
# different numbers.
#
# TODO get rid of this as soon as possible or potentially build in a robust
# way to add in course-specific styling. There needs to be a discussion
# about the right way to do this, but arjun will address this ASAP. Also
# note that the courseware template needs to change when this is removed.
@property
def css_class(self):
return self.metadata.get('css_class', '')
@property
def info_sidebar_name(self):
return self.metadata.get('info_sidebar_name', 'Course Handouts')
@property
def discussion_link(self):
"""TODO: This is a quick kludge to allow CS50 (and other courses) to
specify their own discussion forums as external links by specifying a
"discussion_link" in their policy JSON file. This should later get
folded in with Syllabus, Course Info, and additional Custom tabs in a
more sensible framework later."""
return self.metadata.get('discussion_link', None)
@property
def forum_posts_allowed(self):
try:
blackout_periods = [(parse_time(start), parse_time(end))
for start, end
in self.metadata.get('discussion_blackouts', [])]
now = time.gmtime()
for start, end in blackout_periods:
if start <= now <= end:
return False
except:
log.exception("Error parsing discussion_blackouts for course {0}".format(self.id))
return True
@property
def hide_progress_tab(self):
"""TODO: same as above, intended to let internal CS50 hide the progress tab
until we get grade integration set up."""
# Explicit comparison to True because we always want to return a bool.
return self.metadata.get('hide_progress_tab') == True
@property
def title(self):
return self.display_name
@property
def number(self):
return self.location.course
@property
def org(self):
return self.location.org
......@@ -3,8 +3,6 @@ import logging
from lxml import etree
from .mako_module import MakoModuleDescriptor
from .xml_module import XmlDescriptor
from .xmodule import XModule
from .progress import Progress
from .exceptions import NotFoundError
......@@ -110,30 +108,3 @@ class SequenceModule(XModule):
new_class = c
return new_class
class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
mako_template = 'widgets/sequence-edit.html'
module_class = SequenceModule
stores_state = True # For remembering where in the sequence the student is
template_dir_name = 'sequence'
@classmethod
def definition_from_xml(cls, xml_object, system):
children = []
for child in xml_object:
try:
children.append(system.process_xml(etree.tostring(child)).location.url())
except:
log.exception("Unable to load child when parsing Sequence. Continuing...")
continue
return {'children': children}
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)))
return xml_object
......@@ -8,11 +8,11 @@ class Usage(namedtuple('Usage', 'id source settings children')):
@classmethod
def create_usage(cls, source):
xmodule = xmodule.get_module(source)
#module = xmodule.get_module(source)
return Usage(
uuid(),
xmodule.id,
xmodule.course_settings,
"UUID",
"Foo",
{},
[],
)
......@@ -31,6 +31,9 @@ def load_usage(usage_tree):
settings: default settings values set by the source xmodule
children: child usages
"""
if usage_tree is None:
return None
usage_tree['children'] = [load_usage(child) for child in usage_tree['children']]
return Usage(**usage_tree)
......@@ -44,5 +47,5 @@ class StructureModule(XModule):
@property
def usage_tree(self):
if self._usage_tree is None:
self._usage_tree = load_usage(self.content['usage_tree'])
self._usage_tree = load_usage(self.content.get('usage_tree', None))
return self._usage_tree
from .xmodule import XModule, register_view
from .seq_module import SequenceDescriptor
from .progress import Progress
from .module_resources import render_template
......@@ -31,7 +30,3 @@ class VerticalModule(XModule):
if c in child_classes:
new_class = c
return new_class
class VerticalDescriptor(SequenceDescriptor):
module_class = VerticalModule
......@@ -25,6 +25,9 @@ def dummy_track(event_type, event):
class ModuleMissingError(Exception):
pass
class MissingXModuleView(Exception):
pass
class Plugin(object):
"""
......@@ -83,64 +86,13 @@ class Plugin(object):
in pkg_resources.iter_entry_points(cls.entry_point)]
class HTMLSnippet(object):
"""
A base class defining an interface for an object that is able to present an
html snippet, along with associated javascript and css
"""
js = {}
js_module_name = None
css = {}
@classmethod
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
All of these will be loaded onto the page in the CMS
"""
return cls.js
@classmethod
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
"""
return cls.css
def get_html(self):
"""
Return the html used to display this snippet
"""
raise NotImplementedError(
"get_html() must be provided by specific modules - not present in {0}"
.format(self.__class__))
def register_view(view_name):
def wrapper(fn):
setattr(fn, 'view_name', view_name)
return fn
return wrapper
class XModule(Plugin, HTMLSnippet):
class XModule(Plugin):
''' Implements a generic learning module.
Subclasses must at a minimum provide a definition for get_html in order
......@@ -149,14 +101,6 @@ class XModule(Plugin, HTMLSnippet):
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
icon_class = 'other'
entry_point = "xmodule.v2"
def __init__(self, runtime, content, course_settings, user_preferences, student_state, *args, **kwargs):
......@@ -220,650 +164,7 @@ class XModule(Plugin, HTMLSnippet):
def children(self):
return self.runtime.children
@property
def display_name(self):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return self.metadata.get('display_name',
self.url_name.replace('_', ' '))
def get_children(self):
'''
Return module instances for all the children of this module.
'''
if self._loaded_children is None:
child_locations = self.get_children_locations()
children = [self.system.get_module(loc) for loc in child_locations]
# get_module returns None if the current user doesn't have access
# to the location.
self._loaded_children = [c for c in children if c is not None]
return self._loaded_children
def get_children_locations(self):
'''
Returns the locations of each of child modules.
Overriding this changes the behavior of get_children and
anything that uses get_children, such as get_display_items.
This method will not instantiate the modules of the children
unless absolutely necessary, so it is cheaper to call than get_children
These children will be the same children returned by the
descriptor unless descriptor.has_dynamic_children() is true.
'''
return self.definition.get('children', [])
def get_display_items(self):
'''
Returns a list of descendent module instances that will display
immediately inside this module.
'''
items = []
for child in self.get_children():
items.extend(child.displayable_items())
return items
def displayable_items(self):
'''
Returns list of displayable modules contained by this module. If this
module is visible, should return [self].
'''
return [self]
def get_icon_class(self):
'''
Return a css class identifying this module in the context of an icon
'''
return self.icon_class
### Functions used in the LMS
def get_instance_state(self):
''' State of the object, as stored in the database
'''
return '{}'
def get_shared_state(self):
'''
Get state that should be shared with other instances
using the same 'shared_state_key' attribute.
'''
return '{}'
def get_score(self):
''' Score the student received on the problem.
'''
return None
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.
'''
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.
If this module has no notion of progress, return None.
'''
return None
def handle_ajax(self, dispatch, get):
''' dispatch is last part of the URL.
get is a dictionary-like object '''
return ""
# cdodge: added to support dynamic substitutions of
# links for courseware assets (e.g. images). <link> is passed through from lxml.html parser
def rewrite_content_links(self, link):
# see if we start with our format, e.g. 'xasset:<filename>'
if link.startswith(XASSET_SRCREF_PREFIX):
# yes, then parse out the name
name = link[len(XASSET_SRCREF_PREFIX):]
loc = Location(self.location)
# resolve the reference to our internal 'filepath' which
link = StaticContent.compute_location_filename(loc.org, loc.course, name)
return link
def policy_key(location):
"""
Get the key for a location in a policy file. (Since the policy file is
specific to a course, it doesn't need the full location url).
"""
return '{cat}/{name}'.format(cat=location.category, name=location.name)
Template = namedtuple("Template", "metadata data children")
class ResourceTemplates(object):
@classmethod
def templates(cls):
"""
Returns a list of Template objects that describe possible templates that can be used
to create a module of this type.
If no templates are provided, there will be no way to create a module of
this type
Expects a class attribute template_dir_name that defines the directory
inside the 'templates' resource directory to pull templates from
"""
templates = []
dirname = os.path.join('templates', cls.template_dir_name)
if not resource_isdir(__name__, dirname):
log.warning("No resource directory {dir} found when loading {cls_name} templates".format(
dir=dirname,
cls_name=cls.__name__,
))
return []
for template_file in resource_listdir(__name__, dirname):
template_content = resource_string(__name__, os.path.join(dirname, template_file))
template = yaml.load(template_content)
templates.append(Template(**template))
return templates
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
"""
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).
"""
entry_point = "xmodule.v1"
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
inheritable_metadata = (
'graded', 'start', 'due', 'graceperiod', 'showanswer', 'rerandomize',
# TODO (ichuang): used for Fall 2012 xqa server access
'xqa_key',
# 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'
)
# cdodge: this is a list of metadata names which are 'system' metadata
# and should not be edited by an end-user
system_metadata_fields = [ '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')
# Name of resource directory to load templates from
template_dir_name = "default"
# ============================= 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).
This allows for maximal flexibility to add to the interface while
preserving backwards compatibility.
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
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
url_name: The name to use for this module in urls and other places
where a unique name is needed.
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
showanswer (string): When to show answers for this module
rerandomize (string): When to generate a newly randomized
instance of the module data
"""
self.system = system
self.metadata = kwargs.get('metadata', {})
self.definition = definition if definition is not None else {}
self.location = Location(kwargs.get('location'))
self.url_name = self.location.name
self.category = self.location.category
self.shared_state_key = kwargs.get('shared_state_key')
self._child_instances = None
self._inherited_metadata = set()
@property
def display_name(self):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return self.metadata.get('display_name',
self.url_name.replace('_', ' '))
@property
def start(self):
"""
If self.metadata contains start, return it. Else return None.
"""
if 'start' not in self.metadata:
return None
return self._try_parse_time('start')
@property
def own_metadata(self):
"""
Return the metadata that is not inherited, but was defined on this module.
"""
return dict((k, v) for k, v in self.metadata.items()
if k not in self._inherited_metadata)
@staticmethod
def compute_inherited_metadata(node):
"""Given a descriptor, traverse all of its descendants and do metadata
inheritance. Should be called on a CourseDescriptor after importing a
course.
NOTE: This means that there is no such thing as lazy loading at the
moment--this accesses all the children."""
for c in node.get_children():
c.inherit_metadata(node.metadata)
XModuleDescriptor.compute_inherited_metadata(c)
def inherit_metadata(self, metadata):
"""
Updates this module with metadata inherited from a containing module.
Only metadata specified in self.inheritable_metadata will
be inherited
"""
# Set all inheritable metadata from kwargs that are
# in self.inheritable_metadata and aren't already set in metadata
for attr in self.inheritable_metadata:
if attr not in self.metadata and attr in metadata:
self._inherited_metadata.add(attr)
self.metadata[attr] = metadata[attr]
def get_children(self):
"""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', []):
child = self.system.load_item(child_loc)
# TODO (vshnayder): this should go away once we have
# proper inheritance support in mongo. The xml
# datastore does all inheritance on course load.
child.inherit_metadata(self.metadata)
self._child_instances.append(child)
return self._child_instances
def get_child_by_url_name(self, url_name):
"""
Return a child XModuleDescriptor with the specified url_name, if it exists, and None otherwise.
"""
for c in self.get_children():
if c.url_name == url_name:
return c
return None
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
instantiated XModule
"""
return partial(
self.module_class,
system,
self.location,
self.definition,
self,
metadata=self.metadata
)
def has_dynamic_children(self):
"""
Returns True if this descriptor has dynamic children for a given
student when the module is created.
Returns False if the children of this descriptor are the same
children that the module will return for any student.
"""
return False
# ================================= JSON PARSING ===========================
@staticmethod
def load_from_json(json_data, system, default_class=None):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of json_data.
json_data must contain a 'location' element, and must be suitable to be
passed into the subclasses `from_json` method.
"""
class_ = XModuleDescriptor.load_class(
json_data['location']['category'],
default_class
)
return class_.from_json(json_data, system)
@classmethod
def from_json(cls, json_data, system):
"""
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: A DescriptorSystem for interacting with external resources
"""
return cls(system=system, **json_data)
# ================================= XML PARSING ============================
@staticmethod
def load_from_xml(xml_data,
system,
org=None,
course=None,
default_class=None):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of xml_data.
xml_data must be a string containing valid xml
system is an XMLParsingSystem
org and course are optional strings that will be used in the generated
module's url identifiers
"""
class_ = XModuleDescriptor.load_class(
etree.fromstring(xml_data).tag,
default_class
)
# leave next line, commented out - useful for low-level debugging
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (
# etree.fromstring(xml_data).tag,class_))
return class_.from_xml(xml_data, system, org, course)
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children
for this module
system is an XMLParsingSystem
org and course are optional strings that will be used in the generated
module's url identifiers
"""
raise NotImplementedError(
'Modules must implement from_xml to be parsable from xml')
def export_to_xml(self, resource_fs):
"""
Returns an xml string representing this module, and all modules
underneath it. May also write required resources out to resource_fs
Assumes that modules have single parentage (that no module appears twice
in the same course), and that it is thus safe to nest modules as xml
children as appropriate.
The returned XML should be able to be parsed back into an identical
XModuleDescriptor using the from_xml method with the same system, org,
and course
"""
raise NotImplementedError(
'Modules must implement export_to_xml to enable xml export')
# =============================== Testing ==================================
def get_sample_state(self):
"""
Return a list of tuples of instance_state, shared_state. Each tuple
defines a sample case for this module
"""
return [('{}', '{}')]
# =============================== BUILTIN METHODS ==========================
def __eq__(self, other):
eq = (self.__class__ == other.__class__ and
all(getattr(self, attr, None) == getattr(other, attr, None)
for attr in self.equality_attributes))
if not eq:
for attr in self.equality_attributes:
pprint((getattr(self, attr, None),
getattr(other, attr, None),
getattr(self, attr, None) == getattr(other, attr, None)))
return eq
def __repr__(self):
return ("{class_}({system!r}, {definition!r}, location={location!r},"
" metadata={metadata!r})".format(
class_=self.__class__.__name__,
system=self.system,
definition=self.definition,
location=self.location,
metadata=self.metadata
))
# ================================ Internal helpers =======================
def _try_parse_time(self, key):
"""
Parse an optional metadata key containing a time: if present, complain
if it doesn't parse.
Return None if not present or invalid.
"""
if key in self.metadata:
try:
return parse_time(self.metadata[key])
except ValueError as e:
msg = "Descriptor {0} loaded with a bad metadata key '{1}': '{2}'".format(
self.location.url(), self.metadata[key], e)
log.warning(msg)
return None
class DescriptorSystem(object):
def __init__(self, load_item, resources_fs, error_tracker, **kwargs):
"""
load_item: Takes a Location and returns an XModuleDescriptor
resources_fs: A Filesystem object that contains all of the
resources needed for the course
error_tracker: A hook for tracking errors in loading the descriptor.
Used for example to get a list of all non-fatal problems on course
load, and display them to the user.
A function of (error_msg). errortracker.py provides a
handy make_error_tracker() function.
Patterns for using the error handler:
try:
x = access_some_resource()
check_some_format(x)
except SomeProblem as err:
msg = 'Grommet {0} is broken: {1}'.format(x, str(err))
log.warning(msg) # don't rely on tracker to log
# NOTE: we generally don't want content errors logged as errors
self.system.error_tracker(msg)
# work around
return 'Oops, couldn't load grommet'
OR, if not in an exception context:
if not check_something(thingy):
msg = "thingy {0} is broken".format(thingy)
log.critical(msg)
self.system.error_tracker(msg)
NOTE: To avoid duplication, do not call the tracker on errors
that you're about to re-raise---let the caller track them.
"""
self.load_item = load_item
self.resources_fs = resources_fs
self.error_tracker = error_tracker
class XMLParsingSystem(DescriptorSystem):
def __init__(self, load_item, resources_fs, error_tracker, process_xml, policy, **kwargs):
"""
load_item, resources_fs, error_tracker: see DescriptorSystem
policy: a policy dictionary for overriding xml metadata
process_xml: Takes an xml string, and returns a XModuleDescriptor
created from that xml
"""
DescriptorSystem.__init__(self, load_item, resources_fs, error_tracker,
**kwargs)
self.process_xml = process_xml
self.policy = policy
class ModuleSystem(object):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
ModuleSystem objects are passed to x_modules to provide access to system
functionality.
Note that these functions can be closures over e.g. a django request
and user, or other environment-specific info.
'''
def __init__(self,
ajax_url,
track_function,
get_module,
render_template,
replace_urls,
user=None,
filestore=None,
debug=False,
xqueue=None,
node_path="",
anonymous_student_id=''):
'''
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. If the current user does not have
access to that location, returns None.
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.
xqueue - Dict containing XqueueInterface object, as well as parameters
for the specific StudentModule:
xqueue = {'interface': XQueueInterface object,
'callback_url': Callback into the LMS,
'queue_name': Target queuename in Xqueue}
replace_urls - TEMPORARY - A function like static_replace.replace_urls
that capa_module can use to fix up the static urls in
ajax results.
anonymous_student_id - Used for tracking modules with student id
'''
self.ajax_url = ajax_url
self.xqueue = xqueue
self.track_function = track_function
self.filestore = filestore
self.get_module = get_module
self.render_template = render_template
self.DEBUG = self.debug = debug
self.seed = user.id if user is not None else 0
self.replace_urls = replace_urls
self.node_path = node_path
self.anonymous_student_id = anonymous_student_id
self.user_is_staff = user is not None and user.is_staff
def get(self, attr):
''' provide uniform access to attributes (like etree).'''
return self.__dict__.get(attr)
def set(self, attr, val):
'''provide uniform access to attributes (like etree)'''
self.__dict__[attr] = val
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
class XModuleDescriptor():
pass
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