diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 9e82fbe8d4..1b186be3db 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -72,6 +72,102 @@ class CapaModule(XModule): ''' icon_class = 'problem' + 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 + + dom2 = etree.fromstring(definition['data']) + + self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'), + default="closed") + # TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed") + self.explain_available = only_one(dom2.xpath('/problem/@explain_available')) + + display_due_date_string = self.metadata.get('due', None) + if display_due_date_string is not None: + self.display_due_date = dateutil.parser.parse(display_due_date_string) + #log.debug("Parsed " + display_due_date_string + " to " + str(self.display_due_date)) + else: + self.display_due_date = None + + grace_period_string = self.metadata.get('graceperiod', None) + if grace_period_string is not None and self.display_due_date: + self.grace_period = parse_timedelta(grace_period_string) + self.close_date = self.display_due_date + self.grace_period + #log.debug("Then parsed " + grace_period_string + " to closing date" + str(self.close_date)) + else: + self.grace_period = None + self.close_date = self.display_due_date + + self.max_attempts = only_one(dom2.xpath('/problem/@attempts')) + if len(self.max_attempts) > 0: + self.max_attempts = int(self.max_attempts) + else: + self.max_attempts = None + + self.show_answer = self.metadata.get('showanwser', 'closed') + + if self.show_answer == "": + self.show_answer = "closed" + + self.rerandomize = self.metadata.get('rerandomize', 'always') + if self.rerandomize == "" or self.rerandomize == "always" or self.rerandomize == "true": + self.rerandomize = "always" + elif self.rerandomize == "false" or self.rerandomize == "per_student": + self.rerandomize = "per_student" + elif self.rerandomize == "never": + self.rerandomize = "never" + else: + raise Exception("Invalid rerandomize attribute " + self.rerandomize) + + if instance_state != None: + instance_state = json.loads(instance_state) + if instance_state != None and 'attempts' in instance_state: + self.attempts = instance_state['attempts'] + + # TODO: Should be: self.filename=only_one(dom2.xpath('/problem/@filename')) + self.filename = "problems/" + only_one(dom2.xpath('/problem/@filename')) + ".xml" + self.name = only_one(dom2.xpath('/problem/@name')) + + weight_string = only_one(dom2.xpath('/problem/@weight')) + if weight_string: + self.weight = float(weight_string) + else: + self.weight = 1 + + if self.rerandomize == 'never': + seed = 1 + elif self.rerandomize == "per_student" and hasattr(system, 'id'): + seed = system.id + else: + seed = None + try: + fp = self.system.filestore.open(self.filename) + except Exception: + log.exception('cannot open file %s' % self.filename) + if self.system.DEBUG: + # create a dummy problem instead of failing + fp = StringIO.StringIO('Problem file %s is missing' % self.filename) + fp.name = "StringIO" + else: + raise + try: + self.lcp = LoncapaProblem(fp, self.location.html_id(), instance_state, seed=seed, system=self.system) + except Exception: + msg = 'cannot create LoncapaProblem %s' % self.filename + log.exception(msg) + if self.system.DEBUG: + msg = '

%s

' % msg.replace('<', '<') + msg += '

%s

' % traceback.format_exc().replace('<', '<') + # create a dummy problem with error message instead of failing + fp = StringIO.StringIO('Problem file %s has an error:%s' % (self.filename, msg)) + fp.name = "StringIO" + self.lcp = LoncapaProblem(fp, self.location.html_id(), instance_state, seed=seed, system=self.system) + else: + raise + def get_instance_state(self): state = self.lcp.get_state() state['attempts'] = self.attempts @@ -171,102 +267,6 @@ class CapaModule(XModule): return html - 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 - - dom2 = etree.fromstring(definition['data']) - - self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'), - default="closed") - # TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed") - self.explain_available = only_one(dom2.xpath('/problem/@explain_available')) - - display_due_date_string = self.metadata.get('due', None) - if display_due_date_string is not None: - self.display_due_date = dateutil.parser.parse(display_due_date_string) - #log.debug("Parsed " + display_due_date_string + " to " + str(self.display_due_date)) - else: - self.display_due_date = None - - grace_period_string = self.metadata.get('graceperiod', None) - if grace_period_string is not None and self.display_due_date: - self.grace_period = parse_timedelta(grace_period_string) - self.close_date = self.display_due_date + self.grace_period - #log.debug("Then parsed " + grace_period_string + " to closing date" + str(self.close_date)) - else: - self.grace_period = None - self.close_date = self.display_due_date - - self.max_attempts = only_one(dom2.xpath('/problem/@attempts')) - if len(self.max_attempts) > 0: - self.max_attempts = int(self.max_attempts) - else: - self.max_attempts = None - - self.show_answer = self.metadata.get('showanwser', 'closed') - - if self.show_answer == "": - self.show_answer = "closed" - - self.rerandomize = self.metadata.get('rerandomize', 'always') - if self.rerandomize == "" or self.rerandomize == "always" or self.rerandomize == "true": - self.rerandomize = "always" - elif self.rerandomize == "false" or self.rerandomize == "per_student": - self.rerandomize = "per_student" - elif self.rerandomize == "never": - self.rerandomize = "never" - else: - raise Exception("Invalid rerandomize attribute " + self.rerandomize) - - if instance_state != None: - instance_state = json.loads(instance_state) - if instance_state != None and 'attempts' in instance_state: - self.attempts = instance_state['attempts'] - - # TODO: Should be: self.filename=only_one(dom2.xpath('/problem/@filename')) - self.filename = "problems/" + only_one(dom2.xpath('/problem/@filename')) + ".xml" - self.name = only_one(dom2.xpath('/problem/@name')) - - weight_string = only_one(dom2.xpath('/problem/@weight')) - if weight_string: - self.weight = float(weight_string) - else: - self.weight = 1 - - if self.rerandomize == 'never': - seed = 1 - elif self.rerandomize == "per_student" and hasattr(system, 'id'): - seed = system.id - else: - seed = None - try: - fp = self.system.filestore.open(self.filename) - except Exception: - log.exception('cannot open file %s' % self.filename) - if self.system.DEBUG: - # create a dummy problem instead of failing - fp = StringIO.StringIO('Problem file %s is missing' % self.filename) - fp.name = "StringIO" - else: - raise - try: - self.lcp = LoncapaProblem(fp, self.location.html_id(), instance_state, seed=seed, system=self.system) - except Exception: - msg = 'cannot create LoncapaProblem %s' % self.filename - log.exception(msg) - if self.system.DEBUG: - msg = '

%s

' % msg.replace('<', '<') - msg += '

%s

' % traceback.format_exc().replace('<', '<') - # create a dummy problem with error message instead of failing - fp = StringIO.StringIO('Problem file %s has an error:%s' % (self.filename, msg)) - fp.name = "StringIO" - self.lcp = LoncapaProblem(fp, self.location.html_id(), instance_state, seed=seed, system=self.system) - else: - raise - def handle_ajax(self, dispatch, get): ''' This is called by courseware.module_render, to handle an AJAX call. diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index af8563c442..b11d540143 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -18,6 +18,22 @@ class_priority = ['video', 'problem'] class SequenceModule(XModule): ''' Layout module which lays out content in a temporal 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) + self.position = 1 + + if instance_state is not None: + state = json.loads(instance_state) + if 'position' in state: + self.position = int(state['position']) + + # if position is specified in system, then use that instead + if system.get('position'): + self.position = int(system.get('position')) + + self.rendered = False + def get_instance_state(self): return json.dumps({'position': self.position}) @@ -81,21 +97,6 @@ class SequenceModule(XModule): new_class = c return new_class - 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: - state = json.loads(instance_state) - if 'position' in state: - self.position = int(state['position']) - - # if position is specified in system, then use that instead - if system.get('position'): - self.position = int(system.get('position')) - - self.rendered = False - class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): mako_template = 'widgets/sequence-edit.html' diff --git a/common/lib/xmodule/template_module.py b/common/lib/xmodule/template_module.py index 52c05616cf..1057fc2a25 100644 --- a/common/lib/xmodule/template_module.py +++ b/common/lib/xmodule/template_module.py @@ -26,8 +26,6 @@ class CustomTagModule(XModule): Renders to:: More information given in the text """ - def get_html(self): - return self.html def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) @@ -36,6 +34,8 @@ class CustomTagModule(XModule): params = dict(xmltree.items()) self.html = self.system.render_template(filename, params, namespace='custom_tags') + def get_html(self): + return self.html class CustomTagDescriptor(RawDescriptor): module_class = CustomTagModule diff --git a/common/lib/xmodule/vertical_module.py b/common/lib/xmodule/vertical_module.py index 6008eb4226..c9ecc5ea18 100644 --- a/common/lib/xmodule/vertical_module.py +++ b/common/lib/xmodule/vertical_module.py @@ -9,6 +9,11 @@ class_priority = ['video', 'problem'] class VerticalModule(XModule): ''' Layout module for laying out submodules vertically.''' + + 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.contents = None + def get_html(self): if self.contents is None: self.contents = [child.get_html() for child in self.get_display_items()] @@ -32,10 +37,6 @@ class VerticalModule(XModule): new_class = c return new_class - 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.contents = None - class VerticalDescriptor(SequenceDescriptor): module_class = VerticalModule diff --git a/common/lib/xmodule/video_module.py b/common/lib/xmodule/video_module.py index 4aa469db7f..ed44a2d422 100644 --- a/common/lib/xmodule/video_module.py +++ b/common/lib/xmodule/video_module.py @@ -13,6 +13,18 @@ class VideoModule(XModule): video_time = 0 icon_class = 'video' + def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): + XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) + xmltree = etree.fromstring(self.definition['data']) + self.youtube = xmltree.get('youtube') + self.name = xmltree.get('name') + self.position = 0 + + if instance_state is not None: + state = json.loads(instance_state) + if 'position' in state: + self.position = int(float(state['position'])) + def handle_ajax(self, dispatch, get): ''' Handle ajax calls to this video. @@ -52,18 +64,6 @@ class VideoModule(XModule): 'name': self.name, }) - def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): - XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) - xmltree = etree.fromstring(self.definition['data']) - self.youtube = xmltree.get('youtube') - self.name = xmltree.get('name') - self.position = 0 - - if instance_state is not None: - state = json.loads(instance_state) - if 'position' in state: - self.position = int(float(state['position'])) - class VideoDescriptor(RawDescriptor): module_class = VideoModule diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index c027d1d774..20a52642d1 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -189,6 +189,46 @@ class XModuleDescriptor(Plugin): # A list of metadata that this module can inherit from its parent module inheritable_metadata = ('graded', 'due', 'graceperiod', 'showanswer', 'rerandomize') + 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: An XModuleSystem for interacting with external resources + definition: A dict containing `data` and `children` representing the problem definition + + Current arguments passed in kwargs: + location: A keystore.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 + format: The format of this module ('Homework', 'Lab', etc) + graded (bool): Whether this module is should be graded or not + 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.definition = definition if definition is not None else {} + self.name = Location(kwargs.get('location')).name + self.type = Location(kwargs.get('location')).category + self.url = Location(kwargs.get('location')).url() + self.metadata = kwargs.get('metadata', {}) + self.shared_state_key = kwargs.get('shared_state_key') + + self._child_instances = None + @staticmethod def load_from_json(json_data, system, default_class=None): """ @@ -269,45 +309,6 @@ class XModuleDescriptor(Plugin): """ return self.js_module - 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: An XModuleSystem for interacting with external resources - definition: A dict containing `data` and `children` representing the problem definition - - Current arguments passed in kwargs: - location: A keystore.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 - format: The format of this module ('Homework', 'Lab', etc) - graded (bool): Whether this module is should be graded or not - 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.definition = definition if definition is not None else {} - self.name = Location(kwargs.get('location')).name - self.type = Location(kwargs.get('location')).category - self.url = Location(kwargs.get('location')).url() - self.metadata = kwargs.get('metadata', {}) - self.shared_state_key = kwargs.get('shared_state_key') - - self._child_instances = None def inherit_metadata(self, metadata): """