Commit 512269f8 by Peter Fogg

Add descriptor for modules with empty XML data.

This allows a more general approach to modules such as word_cloud and
video which have no XML data (it's all store in metadata), but use
XmlDescriptor for backwards compatibility. They now generate an empty
tag on export, and clear out empty tags on import.

Also a small change to the video module as a result -- if it's asked
to parse empty XML data, it won't try to parse anything.
parent f355e4a8
...@@ -883,6 +883,40 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -883,6 +883,40 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
shutil.rmtree(root_dir) shutil.rmtree(root_dir)
def test_empty_data_roundtrip(self):
"""
Test that an empty `data` field is preserved through
export/import.
"""
module_store = modulestore('direct')
draft_store = modulestore('draft')
content_store = contentstore()
import_from_xml(module_store, 'common/test/data/', ['toy'])
location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
verticals = module_store.get_items(['i4x', 'edX', 'toy', 'vertical', None, None])
self.assertGreater(len(verticals), 0)
parent = verticals[0]
# Create a module, and ensure that its `data` field is empty
word_cloud = ItemFactory.create(parent_location=parent.location, category="word_cloud", display_name="untitled")
del word_cloud.data
self.assertEquals(word_cloud.data, '')
# Export the course
root_dir = path(mkdtemp_clean())
export_to_xml(module_store, content_store, location, root_dir, 'test_roundtrip', draft_modulestore=draft_store)
# Reimport and get the video back
import_from_xml(module_store, root_dir)
imported_word_cloud = module_store.get_item(Location(['i4x', 'edX', 'toy', 'word_cloud', 'untitled', None]))
# It should now contain empty data
self.assertEquals(imported_word_cloud.data, '')
def test_course_handouts_rewrites(self): def test_course_handouts_rewrites(self):
module_store = modulestore('direct') module_store = modulestore('direct')
......
...@@ -20,8 +20,6 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor): ...@@ -20,8 +20,6 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, [] return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, []
def definition_to_xml(self, resource_fs): def definition_to_xml(self, resource_fs):
if not self.data:
return etree.fromstring('<{0} />'.format(self.category))
try: try:
return etree.fromstring(self.data) return etree.fromstring(self.data)
except etree.XMLSyntaxError as err: except etree.XMLSyntaxError as err:
...@@ -34,3 +32,22 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor): ...@@ -34,3 +32,22 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
context=lines[line - 1][offset - 40:offset + 40], context=lines[line - 1][offset - 40:offset + 40],
loc=self.location)) loc=self.location))
raise Exception, msg, sys.exc_info()[2] raise Exception, msg, sys.exc_info()[2]
class EmptyDataRawDescriptor(XmlDescriptor, XMLEditingDescriptor):
"""
Version of RawDescriptor for modules which may have no XML data,
but use XMLEditingDescriptor for import/export handling.
"""
data = String(default='', scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system):
if len(xml_object) == 0 and len(xml_object.items()) == 0:
return {'data': ''}, []
return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, []
def definition_to_xml(self, resource_fs):
if self.data:
return etree.fromstring(self.data)
return etree.Element(self.category)
...@@ -12,7 +12,7 @@ import time ...@@ -12,7 +12,7 @@ import time
from django.http import Http404 from django.http import Http404
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xblock.core import Integer, Scope, String, Float, Boolean from xblock.core import Integer, Scope, String, Float, Boolean
...@@ -97,7 +97,7 @@ class VideoModule(VideoFields, XModule): ...@@ -97,7 +97,7 @@ class VideoModule(VideoFields, XModule):
class VideoDescriptor(VideoFields, class VideoDescriptor(VideoFields,
MetadataOnlyEditingDescriptor, MetadataOnlyEditingDescriptor,
RawDescriptor): EmptyDataRawDescriptor):
module_class = VideoModule module_class = VideoModule
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
...@@ -136,6 +136,9 @@ def _parse_video_xml(video, xml_data): ...@@ -136,6 +136,9 @@ def _parse_video_xml(video, xml_data):
Parse video fields out of xml_data. The fields are set if they are Parse video fields out of xml_data. The fields are set if they are
present in the XML. present in the XML.
""" """
if not xml_data:
return
xml = etree.fromstring(xml_data) xml = etree.fromstring(xml_data)
display_name = xml.get('display_name') display_name = xml.get('display_name')
......
...@@ -10,7 +10,7 @@ import json ...@@ -10,7 +10,7 @@ import json
import logging import logging
from pkg_resources import resource_string from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.x_module import XModule from xmodule.x_module import XModule
...@@ -240,7 +240,7 @@ class WordCloudModule(WordCloudFields, XModule): ...@@ -240,7 +240,7 @@ class WordCloudModule(WordCloudFields, XModule):
return self.content return self.content
class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, RawDescriptor): class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescriptor):
"""Descriptor for WordCloud Xmodule.""" """Descriptor for WordCloud Xmodule."""
module_class = WordCloudModule module_class = WordCloudModule
template_dir_name = 'word_cloud' template_dir_name = 'word_cloud'
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