Commit 344cb133 by Calen Pennington

Make import/export tests pass

parent 6a612a6e
...@@ -61,7 +61,7 @@ class CapaModule(XModule): ...@@ -61,7 +61,7 @@ class CapaModule(XModule):
max_attempts = StringyInt(help="Maximum number of attempts that a student is allowed", scope=Scope.settings) 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) 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) 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) 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) rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content)
...@@ -359,26 +359,26 @@ class CapaModule(XModule): ...@@ -359,26 +359,26 @@ class CapaModule(XModule):
def answer_available(self): def answer_available(self):
''' Is the user allowed to see an answer? ''' Is the user allowed to see an answer?
''' '''
if self.show_answer == '': if self.showanswer == '':
return False return False
if self.show_answer == "never": if self.showanswer == "never":
return False return False
# Admins can see the answer, unless the problem explicitly prevents it # Admins can see the answer, unless the problem explicitly prevents it
if self.system.user_is_staff: if self.system.user_is_staff:
return True return True
if self.show_answer == 'attempted': if self.showanswer == 'attempted':
return self.attempts > 0 return self.attempts > 0
if self.show_answer == 'answered': if self.showanswer == 'answered':
return self.done return self.done
if self.show_answer == 'closed': if self.showanswer == 'closed':
return self.closed() return self.closed()
if self.show_answer == 'always': if self.showanswer == 'always':
return True return True
return False return False
...@@ -409,7 +409,7 @@ class CapaModule(XModule): ...@@ -409,7 +409,7 @@ class CapaModule(XModule):
''' '''
event_info = dict() event_info = dict()
event_info['problem_id'] = self.location.url() 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(): if not self.answer_available():
raise NotFoundError('Answer is not available') raise NotFoundError('Answer is not available')
else: else:
......
from xmodule.model import Scope 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): def compute_inherited_metadata(descriptor):
"""Given a descriptor, traverse all of its descendants and do metadata """Given a descriptor, traverse all of its descendants and do metadata
...@@ -24,7 +33,7 @@ def inherit_metadata(descriptor, model_data): ...@@ -24,7 +33,7 @@ def inherit_metadata(descriptor, model_data):
# Set all inheritable metadata from kwargs that are # Set all inheritable metadata from kwargs that are
# in self.inheritable_metadata and aren't already set in metadata # 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: if attr not in descriptor._model_data and attr in model_data:
descriptor._inherited_metadata.add(attr) descriptor._inherited_metadata.add(attr)
descriptor._model_data[attr] = model_data[attr] descriptor._model_data[attr] = model_data[attr]
......
...@@ -73,8 +73,8 @@ class SelfAssessmentModule(XModule): ...@@ -73,8 +73,8 @@ class SelfAssessmentModule(XModule):
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content) rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content) prompt = String(scope=Scope.content)
submit_message = String(scope=Scope.content) submitmessage = String(scope=Scope.content)
hint_prompt = String(scope=Scope.content) hintprompt = String(scope=Scope.content)
def _allow_reset(self): def _allow_reset(self):
"""Can the module be reset?""" """Can the module be reset?"""
...@@ -209,7 +209,7 @@ class SelfAssessmentModule(XModule): ...@@ -209,7 +209,7 @@ class SelfAssessmentModule(XModule):
else: else:
hint = '' hint = ''
context = {'hint_prompt': self.hint_prompt, context = {'hint_prompt': self.hintprompt,
'hint': hint} 'hint': hint}
if self.state == self.REQUEST_HINT: if self.state == self.REQUEST_HINT:
...@@ -228,7 +228,7 @@ class SelfAssessmentModule(XModule): ...@@ -228,7 +228,7 @@ class SelfAssessmentModule(XModule):
if self.state != self.DONE: if self.state != self.DONE:
return "" return ""
return """<div class="save_message">{0}</div>""".format(self.submit_message) return """<div class="save_message">{0}</div>""".format(self.submitmessage)
def save_answer(self, get): def save_answer(self, get):
...@@ -397,8 +397,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -397,8 +397,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content) rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content) prompt = String(scope=Scope.content)
submit_message = String(scope=Scope.content) submitmessage = String(scope=Scope.content)
hint_prompt = String(scope=Scope.content) hintprompt = String(scope=Scope.content)
@classmethod @classmethod
def definition_from_xml(cls, xml_object, system): def definition_from_xml(cls, xml_object, system):
......
...@@ -12,6 +12,7 @@ from xmodule.errortracker import make_error_tracker ...@@ -12,6 +12,7 @@ from xmodule.errortracker import make_error_tracker
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import compute_inherited_metadata
from .test_export import DATA_DIR from .test_export import DATA_DIR
...@@ -134,6 +135,7 @@ class ImportTestCase(unittest.TestCase): ...@@ -134,6 +135,7 @@ class ImportTestCase(unittest.TestCase):
</chapter> </chapter>
</course>'''.format(due=v, org=ORG, course=COURSE, url_name=url_name) </course>'''.format(due=v, org=ORG, course=COURSE, url_name=url_name)
descriptor = system.process_xml(start_xml) descriptor = system.process_xml(start_xml)
compute_inherited_metadata(descriptor)
print descriptor, descriptor._model_data print descriptor, descriptor._model_data
self.assertEqual(descriptor.lms.due, v) self.assertEqual(descriptor.lms.due, v)
......
...@@ -281,21 +281,15 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): ...@@ -281,21 +281,15 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
__metaclass__ = XModuleMetaclass __metaclass__ = XModuleMetaclass
# Attributes for inspection of the descriptor # 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) # 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 # It should respond to max_score() and grade(). It can be graded or ungraded
# (like a practice problem). # (like a practice problem).
has_score = False
# 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 # cdodge: this is a list of metadata names which are 'system' metadata
# and should not be edited by an end-user # and should not be edited by an end-user
......
from xmodule.x_module import (XModuleDescriptor, policy_key) from xmodule.x_module import (XModuleDescriptor, policy_key)
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from xmodule.model import Object, Scope
from lxml import etree from lxml import etree
import json import json
import copy import copy
...@@ -78,6 +79,8 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -78,6 +79,8 @@ class XmlDescriptor(XModuleDescriptor):
Mixin class for standardized parsing of from xml 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 # Extension to append to filename paths
filename_extension = 'xml' filename_extension = 'xml'
...@@ -102,7 +105,9 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -102,7 +105,9 @@ class XmlDescriptor(XModuleDescriptor):
'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date', 'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date',
'discussion_blackouts', 'discussion_blackouts',
# VS[compat] -- remove the below attrs once everything is in the CMS # 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') metadata_to_export_to_policy = ('discussion_topics')
...@@ -319,6 +324,11 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -319,6 +324,11 @@ class XmlDescriptor(XModuleDescriptor):
model_data.update(definition) model_data.update(definition)
model_data['children'] = children 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( return cls(
system, system,
location, location,
...@@ -343,7 +353,7 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -343,7 +353,7 @@ class XmlDescriptor(XModuleDescriptor):
def export_to_xml(self, resource_fs): 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 underneath it. May also write required resources out to resource_fs
Assumes that modules have single parentage (that no module appears twice Assumes that modules have single parentage (that no module appears twice
...@@ -379,6 +389,10 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -379,6 +389,10 @@ class XmlDescriptor(XModuleDescriptor):
#logging.debug('location.category = {0}, attr = {1}'.format(self.location.category, attr)) #logging.debug('location.category = {0}, attr = {1}'.format(self.location.category, attr))
xml_object.set(attr, val) 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(): if self.export_to_file():
# Write the definition to a file # Write the definition to a file
url_path = name_to_pathname(self.url_name) url_path = name_to_pathname(self.url_name)
......
...@@ -40,3 +40,6 @@ class LmsNamespace(Namespace): ...@@ -40,3 +40,6 @@ class LmsNamespace(Namespace):
xqa_key = String(help="DO NOT USE", scope=Scope.settings) 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) 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) 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)
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