diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index b15c05b984ee..78ff575c35eb 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -855,6 +855,68 @@ def test_export_course_with_metadata_only_video(self): shutil.rmtree(root_dir) + def test_export_course_with_metadata_only_word_cloud(self): + """ + Similar to `test_export_course_with_metadata_only_video`. + """ + module_store = modulestore('direct') + draft_store = modulestore('draft') + content_store = contentstore() + + import_from_xml(module_store, 'common/test/data/', ['word_cloud']) + location = CourseDescriptor.id_to_location('HarvardX/ER22x/2013_Spring') + + verticals = module_store.get_items(['i4x', 'HarvardX', 'ER22x', 'vertical', None, None]) + + self.assertGreater(len(verticals), 0) + + parent = verticals[0] + + ItemFactory.create(parent_location=parent.location, category="word_cloud", display_name="untitled") + + root_dir = path(mkdtemp_clean()) + + print 'Exporting to tempdir = {0}'.format(root_dir) + + # export out to a tempdir + export_to_xml(module_store, content_store, location, root_dir, 'test_export', draft_modulestore=draft_store) + + 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): module_store = modulestore('direct') diff --git a/common/lib/xmodule/xmodule/raw_module.py b/common/lib/xmodule/xmodule/raw_module.py index 4c6c719224c9..b972d7c8eb3a 100644 --- a/common/lib/xmodule/xmodule/raw_module.py +++ b/common/lib/xmodule/xmodule/raw_module.py @@ -32,3 +32,22 @@ def definition_to_xml(self, resource_fs): context=lines[line - 1][offset - 40:offset + 40], loc=self.location)) 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) diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 6c5640346ee7..763975fc3bc9 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -12,7 +12,7 @@ from django.http import Http404 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 xblock.core import Integer, Scope, String, Float, Boolean @@ -97,7 +97,7 @@ def get_html(self): class VideoDescriptor(VideoFields, MetadataOnlyEditingDescriptor, - RawDescriptor): + EmptyDataRawDescriptor): module_class = VideoModule def __init__(self, *args, **kwargs): @@ -130,19 +130,15 @@ def from_xml(cls, xml_data, system, org=None, course=None): _parse_video_xml(video, video.data) return video - def definition_to_xml(self, resource_fs): - """ - Override the base implementation. We don't actually have anything in the 'data' field - (it's an empty string), so we just return a simple XML element - """ - return etree.Element('video') - def _parse_video_xml(video, xml_data): """ Parse video fields out of xml_data. The fields are set if they are present in the XML. """ + if not xml_data: + return + xml = etree.fromstring(xml_data) display_name = xml.get('display_name') diff --git a/common/lib/xmodule/xmodule/word_cloud_module.py b/common/lib/xmodule/xmodule/word_cloud_module.py index 004e6ed32064..900bded760f9 100644 --- a/common/lib/xmodule/xmodule/word_cloud_module.py +++ b/common/lib/xmodule/xmodule/word_cloud_module.py @@ -10,7 +10,7 @@ import logging 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.x_module import XModule @@ -240,7 +240,7 @@ def get_html(self): return self.content -class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, RawDescriptor): +class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescriptor): """Descriptor for WordCloud Xmodule.""" module_class = WordCloudModule template_dir_name = 'word_cloud'