From 344cb133aab2c55cf61144e433ee417fa090a368 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 21 Dec 2012 14:16:56 -0500 Subject: [PATCH] Make import/export tests pass --- common/lib/xmodule/xmodule/capa_module.py | 16 ++++++++-------- .../xmodule/xmodule/modulestore/inheritance.py | 11 ++++++++++- .../xmodule/xmodule/self_assessment_module.py | 12 ++++++------ .../lib/xmodule/xmodule/tests/test_import.py | 2 ++ common/lib/xmodule/xmodule/x_module.py | 18 ++++++------------ common/lib/xmodule/xmodule/xml_module.py | 18 ++++++++++++++++-- lms/xmodule_namespace.py | 3 +++ 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index f83c31fb5c..7e66ef534e 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -61,7 +61,7 @@ class CapaModule(XModule): max_attempts = StringyInt(help="Maximum number of attempts that a student is allowed", scope=Scope.settings) due = String(help="Date that this problem is due by", scope=Scope.settings) graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings) - show_answer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed") + showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed") force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False) rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings) data = String(help="XML data for the problem", scope=Scope.content) @@ -359,26 +359,26 @@ class CapaModule(XModule): def answer_available(self): ''' Is the user allowed to see an answer? ''' - if self.show_answer == '': + if self.showanswer == '': return False - if self.show_answer == "never": + if self.showanswer == "never": return False # Admins can see the answer, unless the problem explicitly prevents it if self.system.user_is_staff: return True - if self.show_answer == 'attempted': + if self.showanswer == 'attempted': return self.attempts > 0 - if self.show_answer == 'answered': + if self.showanswer == 'answered': return self.done - if self.show_answer == 'closed': + if self.showanswer == 'closed': return self.closed() - if self.show_answer == 'always': + if self.showanswer == 'always': return True return False @@ -409,7 +409,7 @@ class CapaModule(XModule): ''' event_info = dict() event_info['problem_id'] = self.location.url() - self.system.track_function('show_answer', event_info) + self.system.track_function('showanswer', event_info) if not self.answer_available(): raise NotFoundError('Answer is not available') else: diff --git a/common/lib/xmodule/xmodule/modulestore/inheritance.py b/common/lib/xmodule/xmodule/modulestore/inheritance.py index ca88aab8d8..38c592564d 100644 --- a/common/lib/xmodule/xmodule/modulestore/inheritance.py +++ b/common/lib/xmodule/xmodule/modulestore/inheritance.py @@ -1,5 +1,14 @@ from xmodule.model import Scope +# 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' +) def compute_inherited_metadata(descriptor): """Given a descriptor, traverse all of its descendants and do metadata @@ -24,7 +33,7 @@ def inherit_metadata(descriptor, model_data): # Set all inheritable metadata from kwargs that are # in self.inheritable_metadata and aren't already set in metadata - for attr in descriptor.inheritable_metadata: + for attr in INHERITABLE_METADATA: if attr not in descriptor._model_data and attr in model_data: descriptor._inherited_metadata.add(attr) descriptor._model_data[attr] = model_data[attr] diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 034ec01253..ffcd86ba52 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -73,8 +73,8 @@ class SelfAssessmentModule(XModule): max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) rubric = String(scope=Scope.content) prompt = String(scope=Scope.content) - submit_message = String(scope=Scope.content) - hint_prompt = String(scope=Scope.content) + submitmessage = String(scope=Scope.content) + hintprompt = String(scope=Scope.content) def _allow_reset(self): """Can the module be reset?""" @@ -209,7 +209,7 @@ class SelfAssessmentModule(XModule): else: hint = '' - context = {'hint_prompt': self.hint_prompt, + context = {'hint_prompt': self.hintprompt, 'hint': hint} if self.state == self.REQUEST_HINT: @@ -228,7 +228,7 @@ class SelfAssessmentModule(XModule): if self.state != self.DONE: return "" - return """
{0}
""".format(self.submit_message) + return """
{0}
""".format(self.submitmessage) def save_answer(self, get): @@ -397,8 +397,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) rubric = String(scope=Scope.content) prompt = String(scope=Scope.content) - submit_message = String(scope=Scope.content) - hint_prompt = String(scope=Scope.content) + submitmessage = String(scope=Scope.content) + hintprompt = String(scope=Scope.content) @classmethod def definition_from_xml(cls, xml_object, system): diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index d243ca1609..803aceef68 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -12,6 +12,7 @@ from xmodule.errortracker import make_error_tracker from xmodule.modulestore import Location from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore.inheritance import compute_inherited_metadata from .test_export import DATA_DIR @@ -134,6 +135,7 @@ class ImportTestCase(unittest.TestCase): '''.format(due=v, org=ORG, course=COURSE, url_name=url_name) descriptor = system.process_xml(start_xml) + compute_inherited_metadata(descriptor) print descriptor, descriptor._model_data self.assertEqual(descriptor.lms.due, v) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 3fc5cb538e..dc1be3eb08 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -281,21 +281,15 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): __metaclass__ = XModuleMetaclass # Attributes for inspection of the descriptor - stores_state = False # Indicates whether the xmodule state should be + + # 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. + stores_state = 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' - ) + has_score = False # cdodge: this is a list of metadata names which are 'system' metadata # and should not be edited by an end-user diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index d19fc26157..a7c1087db7 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -1,6 +1,7 @@ from xmodule.x_module import (XModuleDescriptor, policy_key) from xmodule.modulestore import Location from xmodule.modulestore.inheritance import own_metadata +from xmodule.model import Object, Scope from lxml import etree import json import copy @@ -78,6 +79,8 @@ class XmlDescriptor(XModuleDescriptor): Mixin class for standardized parsing of from xml """ + xml_attributes = Object(help="Map of unhandled xml attributes, used only for storage between import and export", default={}, scope=Scope.settings) + # Extension to append to filename paths filename_extension = 'xml' @@ -102,7 +105,9 @@ class XmlDescriptor(XModuleDescriptor): 'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date', 'discussion_blackouts', # VS[compat] -- remove the below attrs once everything is in the CMS - 'course', 'org', 'url_name', 'filename') + 'course', 'org', 'url_name', 'filename', + # Used for storing xml attributes between import and export, for roundtrips + 'xml_attributes') metadata_to_export_to_policy = ('discussion_topics') @@ -319,6 +324,11 @@ class XmlDescriptor(XModuleDescriptor): model_data.update(definition) model_data['children'] = children + model_data['xml_attributes'] = {} + for key, value in metadata.items(): + if key not in set(f.name for f in cls.fields + cls.lms.fields): + model_data['xml_attributes'][key] = value + return cls( system, location, @@ -343,7 +353,7 @@ class XmlDescriptor(XModuleDescriptor): def export_to_xml(self, resource_fs): """ - Returns an xml string representing this module, and all modules + Returns an xml string representign 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 @@ -379,6 +389,10 @@ class XmlDescriptor(XModuleDescriptor): #logging.debug('location.category = {0}, attr = {1}'.format(self.location.category, attr)) xml_object.set(attr, val) + for key, value in self.xml_attributes.items(): + if key not in self.metadata_to_strip: + xml_object.set(key, value) + if self.export_to_file(): # Write the definition to a file url_path = name_to_pathname(self.url_name) diff --git a/lms/xmodule_namespace.py b/lms/xmodule_namespace.py index 81a84edeaa..14a19c6714 100644 --- a/lms/xmodule_namespace.py +++ b/lms/xmodule_namespace.py @@ -40,3 +40,6 @@ class LmsNamespace(Namespace): xqa_key = String(help="DO NOT USE", scope=Scope.settings) ispublic = Boolean(help="Whether this course is open to the public, or only to admins", scope=Scope.settings) graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings) + showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed") + rerandomize = String(help="When to rerandomize the problem", default="always", scope=Scope.settings) +