Commit aa1b9fec by Don Mitchell

Properly (de)serialize references in old mongo

LMS-11204
parent 681438f0
...@@ -932,7 +932,7 @@ class TestEditSplitModule(ItemTest): ...@@ -932,7 +932,7 @@ class TestEditSplitModule(ItemTest):
# group_id_to_child and children have not changed yet. # group_id_to_child and children have not changed yet.
split_test = self._assert_children(2) split_test = self._assert_children(2)
group_id_to_child = split_test.group_id_to_child group_id_to_child = split_test.group_id_to_child.copy()
self.assertEqual(2, len(group_id_to_child)) self.assertEqual(2, len(group_id_to_child))
# Test environment and Studio use different module systems # Test environment and Studio use different module systems
......
...@@ -707,8 +707,8 @@ class EdxJSONEncoder(json.JSONEncoder): ...@@ -707,8 +707,8 @@ class EdxJSONEncoder(json.JSONEncoder):
ISO date strings ISO date strings
""" """
def default(self, obj): def default(self, obj):
if isinstance(obj, Location): if isinstance(obj, (CourseKey, UsageKey)):
return obj.to_deprecated_string() return unicode(obj)
elif isinstance(obj, datetime.datetime): elif isinstance(obj, datetime.datetime):
if obj.tzinfo is not None: if obj.tzinfo is not None:
if obj.utcoffset() is None: if obj.utcoffset() is None:
......
...@@ -170,8 +170,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -170,8 +170,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# return the default store # return the default store
return self.default_modulestore return self.default_modulestore
# return the first store, as the default
return self.default_modulestore
@property @property
def default_modulestore(self): def default_modulestore(self):
......
...@@ -238,10 +238,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -238,10 +238,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if not edit_info: if not edit_info:
module.edited_by = module.edited_on = module.subtree_edited_on = \ module.edited_by = module.edited_on = module.subtree_edited_on = \
module.subtree_edited_by = module.published_date = None module.subtree_edited_by = module.published_date = None
raw_metadata = json_data.get('metadata', {})
# published_date was previously stored as a list of time components instead of a datetime # published_date was previously stored as a list of time components instead of a datetime
if metadata.get('published_date'): if raw_metadata.get('published_date'):
module.published_date = datetime(*metadata.get('published_date')[0:6]).replace(tzinfo=UTC) module.published_date = datetime(*raw_metadata.get('published_date')[0:6]).replace(tzinfo=UTC)
module.published_by = metadata.get('published_by') module.published_by = raw_metadata.get('published_by')
# otherwise restore the stored editing information # otherwise restore the stored editing information
else: else:
module.edited_by = edit_info.get('edited_by') module.edited_by = edit_info.get('edited_by')
...@@ -280,22 +281,26 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -280,22 +281,26 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
:param course_key: a CourseKey object for the given course :param course_key: a CourseKey object for the given course
:param jsonfields: a dict of the jsonified version of the fields :param jsonfields: a dict of the jsonified version of the fields
""" """
result = {}
for field_name, value in jsonfields.iteritems(): for field_name, value in jsonfields.iteritems():
if value: field = class_.fields.get(field_name)
field = class_.fields.get(field_name) if field is None:
if field is None: continue
continue elif value is None:
elif isinstance(field, Reference): result[field_name] = value
jsonfields[field_name] = self._convert_reference_to_key(value) elif isinstance(field, Reference):
elif isinstance(field, ReferenceList): result[field_name] = self._convert_reference_to_key(value)
jsonfields[field_name] = [ elif isinstance(field, ReferenceList):
self._convert_reference_to_key(ele) for ele in value result[field_name] = [
] self._convert_reference_to_key(ele) for ele in value
elif isinstance(field, ReferenceValueDict): ]
for key, subvalue in value.iteritems(): elif isinstance(field, ReferenceValueDict):
assert isinstance(subvalue, basestring) result[field_name] = {
value[key] = self._convert_reference_to_key(subvalue) key: self._convert_reference_to_key(subvalue) for key, subvalue in value.iteritems()
return jsonfields }
else:
result[field_name] = value
return result
def lookup_item(self, location): def lookup_item(self, location):
""" """
...@@ -1125,14 +1130,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1125,14 +1130,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
therefore propagate subtree edit info up the tree therefore propagate subtree edit info up the tree
""" """
try: try:
definition_data = self._convert_reference_fields_to_strings( definition_data = self._serialize_scope(xblock, Scope.content)
xblock,
xblock.get_explicitly_set_fields_by_scope()
)
now = datetime.now(UTC) now = datetime.now(UTC)
payload = { payload = {
'definition.data': definition_data, 'definition.data': definition_data,
'metadata': self._convert_reference_fields_to_strings(xblock, own_metadata(xblock)), 'metadata': self._serialize_scope(xblock, Scope.settings),
'edit_info.edited_on': now, 'edit_info.edited_on': now,
'edit_info.edited_by': user_id, 'edit_info.edited_by': user_id,
'edit_info.subtree_edited_on': now, 'edit_info.subtree_edited_on': now,
...@@ -1144,7 +1146,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1144,7 +1146,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
payload['edit_info.published_by'] = user_id payload['edit_info.published_by'] = user_id
if xblock.has_children: if xblock.has_children:
children = self._convert_reference_fields_to_strings(xblock, {'children': xblock.children}) children = self._serialize_scope(xblock, Scope.children)
payload.update({'definition.children': children['children']}) payload.update({'definition.children': children['children']})
self._update_single_item(xblock.scope_ids.usage_id, payload) self._update_single_item(xblock.scope_ids.usage_id, payload)
...@@ -1185,25 +1187,27 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1185,25 +1187,27 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return xblock return xblock
def _convert_reference_fields_to_strings(self, xblock, jsonfields): def _serialize_scope(self, xblock, scope):
""" """
Find all fields of type reference and convert the payload from UsageKeys to deprecated strings Find all fields of type reference and convert the payload from UsageKeys to deprecated strings
:param xblock: the XBlock class :param xblock: the XBlock class
:param jsonfields: a dict of the jsonified version of the fields :param jsonfields: a dict of the jsonified version of the fields
""" """
assert isinstance(jsonfields, dict) jsonfields = {}
for field_name, value in jsonfields.iteritems(): for field_name, field in xblock.fields.iteritems():
if value: if (field.scope == scope and field.is_set_on(xblock)):
if isinstance(xblock.fields[field_name], Reference): if isinstance(field, Reference):
jsonfields[field_name] = value.to_deprecated_string() jsonfields[field_name] = field.read_from(xblock).to_deprecated_string()
elif isinstance(xblock.fields[field_name], ReferenceList): elif isinstance(field, ReferenceList):
jsonfields[field_name] = [ jsonfields[field_name] = [
ele.to_deprecated_string() for ele in value ele.to_deprecated_string() for ele in field.read_from(xblock)
] ]
elif isinstance(xblock.fields[field_name], ReferenceValueDict): elif isinstance(field, ReferenceValueDict):
for key, subvalue in value.iteritems(): jsonfields[field_name] = {
assert isinstance(subvalue, UsageKey) key: unicode(subvalue) for key, subvalue in field.read_from(xblock).iteritems()
value[key] = subvalue.to_deprecated_string() }
else:
jsonfields[field_name] = field.read_json(xblock)
return jsonfields return jsonfields
def _get_raw_parent_location(self, location, revision=ModuleStoreEnum.RevisionOption.published_only): def _get_raw_parent_location(self, location, revision=ModuleStoreEnum.RevisionOption.published_only):
......
...@@ -229,6 +229,8 @@ CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),) ...@@ -229,6 +229,8 @@ CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
COURSE_DATA_NAMES = ( COURSE_DATA_NAMES = (
'toy', 'toy',
'manual-testing-complete', 'manual-testing-complete',
'split_test_module',
'split_test_module_draft',
) )
......
...@@ -16,7 +16,7 @@ from mock import Mock ...@@ -16,7 +16,7 @@ from mock import Mock
from path import path from path import path
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds, Scope from xblock.fields import ScopeIds, Scope, Reference, ReferenceList, ReferenceValueDict
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata
...@@ -159,6 +159,21 @@ class LogicTest(unittest.TestCase): ...@@ -159,6 +159,21 @@ class LogicTest(unittest.TestCase):
return json.loads(self.xmodule.handle_ajax(dispatch, data)) return json.loads(self.xmodule.handle_ajax(dispatch, data))
def map_references(value, field, actual_course_key):
"""
Map the references in value to actual_course_key and return value
"""
if not value: # if falsey
return value
if isinstance(field, Reference):
return value.map_into_course(actual_course_key)
if isinstance(field, ReferenceList):
return [sub.map_into_course(actual_course_key) for sub in value]
if isinstance(field, ReferenceValueDict):
return {key: ele.map_into_course(actual_course_key) for key, ele in value.iteritems()}
return value
class CourseComparisonTest(unittest.TestCase): class CourseComparisonTest(unittest.TestCase):
""" """
Mixin that has methods for comparing courses for equality. Mixin that has methods for comparing courses for equality.
...@@ -239,7 +254,7 @@ class CourseComparisonTest(unittest.TestCase): ...@@ -239,7 +254,7 @@ class CourseComparisonTest(unittest.TestCase):
# compare fields # compare fields
self.assertEqual(expected_item.fields, actual_item.fields) self.assertEqual(expected_item.fields, actual_item.fields)
for field_name in expected_item.fields: for field_name, field in expected_item.fields.iteritems():
if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions: if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions:
continue continue
...@@ -250,8 +265,8 @@ class CourseComparisonTest(unittest.TestCase): ...@@ -250,8 +265,8 @@ class CourseComparisonTest(unittest.TestCase):
if field_name == 'children': if field_name == 'children':
continue continue
exp_value = getattr(expected_item, field_name) exp_value = map_references(field.read_from(expected_item), field, actual_course_key)
actual_value = getattr(actual_item, field_name) actual_value = field.read_from(actual_item)
self.assertEqual( self.assertEqual(
exp_value, exp_value,
actual_value, actual_value,
......
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