diff --git a/cms/djangoapps/contentstore/management/commands/export.py b/cms/djangoapps/contentstore/management/commands/export.py new file mode 100644 index 0000000000..11b043c2ab --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/export.py @@ -0,0 +1,35 @@ +### +### Script for exporting courseware from Mongo to a tar.gz file +### +import os + +from django.core.management.base import BaseCommand, CommandError +from xmodule.modulestore.xml_exporter import export_to_xml +from xmodule.modulestore.django import modulestore +from xmodule.contentstore.django import contentstore +from xmodule.modulestore import Location +from xmodule.course_module import CourseDescriptor + + +unnamed_modules = 0 + + +class Command(BaseCommand): + help = \ +'''Import the specified data directory into the default ModuleStore''' + + def handle(self, *args, **options): + if len(args) != 2: + raise CommandError("import requires two arguments: ") + + course_id = args[0] + output_path = args[1] + + print "Exporting course id = {0} to {1}".format(course_id, output_path) + + location = CourseDescriptor.id_to_location(course_id) + + root_dir = os.path.dirname(output_path) + course_dir = os.path.splitext(os.path.basename(output_path))[0] + + export_to_xml(modulestore('direct'), contentstore(), location, root_dir, course_dir) diff --git a/common/lib/xmodule/xmodule/contentstore/mongo.py b/common/lib/xmodule/xmodule/contentstore/mongo.py index 1579efa9ce..661d0260a7 100644 --- a/common/lib/xmodule/xmodule/contentstore/mongo.py +++ b/common/lib/xmodule/xmodule/contentstore/mongo.py @@ -12,6 +12,7 @@ import logging from .content import StaticContent, ContentStore from xmodule.exceptions import NotFoundError from fs.osfs import OSFS +import os class MongoContentStore(ContentStore): @@ -47,16 +48,30 @@ class MongoContentStore(ContentStore): with self.fs.get(id) as fp: return StaticContent(location, fp.displayname, fp.content_type, fp.read(), fp.uploadDate, thumbnail_location = fp.thumbnail_location if 'thumbnail_location' in fp else None, - import_path = fp.import_path if 'import_path' in fp else None) + import_path = fp.import_path if hasattr(fp, 'import_path') else None) except NoFile: raise NotFoundError() def export(self, location, output_directory): content = self.find(location) + + if content.import_path is not None: + output_directory = output_directory + '/' + os.path.dirname(content.import_path) + + if not os.path.exists(output_directory): + os.makedirs(output_directory) + disk_fs = OSFS(output_directory) - with disk_fs.open('course.xml', 'wb') as course_xml: - course_xml.write(content.data) + with disk_fs.open(content.name, 'wb') as asset_file: + asset_file.write(content.data) + + def export_all_for_course(self, course_location, output_directory): + assets = self.get_all_content_for_course(course_location) + + for asset in assets: + asset_location = Location(asset['_id']) + self.export(asset_location, output_directory) def get_all_content_thumbnails_for_course(self, location): return self._get_all_content_for_course(location, get_thumbnails = True) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py new file mode 100644 index 0000000000..e0bf0ec1d3 --- /dev/null +++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py @@ -0,0 +1,20 @@ +import logging +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from fs.osfs import OSFS + +def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir): + + course = modulestore.get_item(course_location) + + fs = OSFS(root_dir) + export_fs = fs.makeopendir(course_dir) + + xml = course.export_to_xml(export_fs) + with export_fs.open('course.xml', 'w') as course_xml: + course_xml.write(xml) + + # export the static assets + contentstore.export_all_for_course(course_location, root_dir + '/' + course_dir + '/static/') + + \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 1143e05753..91400b7e8a 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -102,6 +102,8 @@ class XmlDescriptor(XModuleDescriptor): # VS[compat] -- remove the below attrs once everything is in the CMS 'course', 'org', 'url_name', 'filename') + metadata_to_export_to_policy = ('discussion_topics') + # A dictionary mapping xml attribute names AttrMaps that describe how # to import and export them # Allow json to specify either the string "true", or the bool True. The string is preferred. @@ -112,6 +114,7 @@ class XmlDescriptor(XModuleDescriptor): # type conversion: want True/False in python, "true"/"false" in xml 'graded': bool_map, 'hide_progress_tab': bool_map, + 'allow_anonymous': bool_map } @@ -359,8 +362,9 @@ class XmlDescriptor(XModuleDescriptor): # Add the non-inherited metadata for attr in sorted(self.own_metadata): # don't want e.g. data_dir - if attr not in self.metadata_to_strip: + if attr not in self.metadata_to_strip and attr not in self.metadata_to_export_to_policy: val = val_for_xml(attr) + # logging.debug('location.category = {0}, attr = {1}'.format(self.location.category, attr)) xml_object.set(attr, val) if self.export_to_file():