Commit 7d76f360 by Carlos Andrés Rocha

Make course xml export work for all modulestores

Also let xml import work on modulestores that don't use write signaling
parent 30861d0b
...@@ -46,12 +46,8 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -46,12 +46,8 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
alongside the public content in the course. alongside the public content in the course.
""" """
# we use get_instance instead of get_item to support modulestores course_id = course_location.course_id
# that can't guarantee that definitions are unique course = modulestore.get_course(course_id)
course = modulestore.get_instance(
course_location.course_id,
course_location
)
fs = OSFS(root_dir) fs = OSFS(root_dir)
export_fs = fs.makeopendir(course_dir) export_fs = fs.makeopendir(course_dir)
...@@ -70,16 +66,16 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -70,16 +66,16 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
) )
# export the static tabs # export the static tabs
export_extra_content(export_fs, modulestore, course_location, 'static_tab', 'tabs', '.html') export_extra_content(export_fs, modulestore, course_id, course_location, 'static_tab', 'tabs', '.html')
# export the custom tags # export the custom tags
export_extra_content(export_fs, modulestore, course_location, 'custom_tag_template', 'custom_tags') export_extra_content(export_fs, modulestore, course_id, course_location, 'custom_tag_template', 'custom_tags')
# export the course updates # export the course updates
export_extra_content(export_fs, modulestore, course_location, 'course_info', 'info', '.html') export_extra_content(export_fs, modulestore, course_id, course_location, 'course_info', 'info', '.html')
# export the 'about' data (e.g. overview, etc.) # export the 'about' data (e.g. overview, etc.)
export_extra_content(export_fs, modulestore, course_location, 'about', 'about', '.html') export_extra_content(export_fs, modulestore, course_id, course_location, 'about', 'about', '.html')
# export the grading policy # export the grading policy
course_run_policy_dir = policies_dir.makeopendir(course.location.name) course_run_policy_dir = policies_dir.makeopendir(course.location.name)
...@@ -112,9 +108,9 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -112,9 +108,9 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
draft_vertical.export_to_xml(draft_course_dir) draft_vertical.export_to_xml(draft_course_dir)
def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix=''): def export_extra_content(export_fs, modulestore, course_id, course_location, category_type, dirname, file_suffix=''):
query_loc = Location('i4x', course_location.org, course_location.course, category_type, None) query_loc = Location('i4x', course_location.org, course_location.course, category_type, None)
items = modulestore.get_items(query_loc) items = modulestore.get_items(query_loc, course_id)
if len(items) > 0: if len(items) > 0:
item_dir = export_fs.makeopendir(dirname) item_dir = export_fs.makeopendir(dirname)
......
...@@ -128,7 +128,9 @@ def import_from_xml(store, data_dir, course_dirs=None, ...@@ -128,7 +128,9 @@ def import_from_xml(store, data_dir, course_dirs=None,
try: try:
# turn off all write signalling while importing as this is a high volume operation # turn off all write signalling while importing as this is a high volume operation
if pseudo_course_id not in store.ignore_write_events_on_courses: # on stores that need it
if (hasattr(store, 'ignore_write_events_on_courses') and
pseudo_course_id not in store.ignore_write_events_on_courses):
store.ignore_write_events_on_courses.append(pseudo_course_id) store.ignore_write_events_on_courses.append(pseudo_course_id)
course_data_path = None course_data_path = None
...@@ -226,8 +228,9 @@ def import_from_xml(store, data_dir, course_dirs=None, ...@@ -226,8 +228,9 @@ def import_from_xml(store, data_dir, course_dirs=None,
) )
finally: finally:
# turn back on all write signalling # turn back on all write signalling on stores that need it
if pseudo_course_id in store.ignore_write_events_on_courses: if (hasattr(store, 'ignore_write_events_on_courses') and
pseudo_course_id in store.ignore_write_events_on_courses):
store.ignore_write_events_on_courses.remove(pseudo_course_id) store.ignore_write_events_on_courses.remove(pseudo_course_id)
store.refresh_cached_metadata_inheritance_tree( store.refresh_cached_metadata_inheritance_tree(
target_location_namespace if target_location_namespace is not None else course_location target_location_namespace if target_location_namespace is not None else course_location
......
...@@ -16,6 +16,7 @@ TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) ...@@ -16,6 +16,7 @@ TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR)
# Map all XML course fixtures so they are accessible through # Map all XML course fixtures so they are accessible through
# the MixedModuleStore # the MixedModuleStore
MAPPINGS = { MAPPINGS = {
'edX/simple/2012_Fall': 'xml',
'edX/toy/2012_Fall': 'xml', 'edX/toy/2012_Fall': 'xml',
'edX/toy/TT_2012_Fall': 'xml', 'edX/toy/TT_2012_Fall': 'xml',
'edX/test_end/2012_Fall': 'xml', 'edX/test_end/2012_Fall': 'xml',
......
...@@ -11,7 +11,9 @@ from path import path ...@@ -11,7 +11,9 @@ from path import path
from django.core.management import call_command from django.core.management import call_command
from django.test.utils import override_settings from django.test.utils import override_settings
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from courseware.tests.modulestore_config import TEST_DATA_MONGO_MODULESTORE
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -19,15 +21,29 @@ from xmodule.modulestore.xml_importer import import_from_xml ...@@ -19,15 +21,29 @@ from xmodule.modulestore.xml_importer import import_from_xml
DATA_DIR = 'common/test/data/' DATA_DIR = 'common/test/data/'
TEST_COURSE_ID = 'edX/simple/2012_Fall'
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class CommandTestCase(ModuleStoreTestCase): class CommandsTestBase(object):
"""Parent class with helpers for testing management commands""" """
Base class for testing different django commands.
Must be subclassed using override_settings set to the modulestore
to be tested.
"""
def setUp(self):
self.loaded_courses = self.load_courses()
def load_courses(self): def load_courses(self):
"""Load test courses and return list of ids""" """Load test courses and return list of ids"""
store = modulestore() store = modulestore()
import_from_xml(store, DATA_DIR, ['toy', 'simple'])
courses = store.get_courses()
if TEST_COURSE_ID not in [c.id for c in courses]:
import_from_xml(store, DATA_DIR, ['toy', 'simple'])
return [course.id for course in store.get_courses()] return [course.id for course in store.get_courses()]
def call_command(self, name, *args, **kwargs): def call_command(self, name, *args, **kwargs):
...@@ -37,13 +53,6 @@ class CommandTestCase(ModuleStoreTestCase): ...@@ -37,13 +53,6 @@ class CommandTestCase(ModuleStoreTestCase):
out.seek(0) out.seek(0)
return out.read() return out.read()
class CommandsTestCase(CommandTestCase):
"""Test case for management commands"""
def setUp(self):
self.loaded_courses = self.load_courses()
def test_dump_course_ids(self): def test_dump_course_ids(self):
kwargs = {'modulestore': 'default'} kwargs = {'modulestore': 'default'}
output = self.call_command('dump_course_ids', **kwargs) output = self.call_command('dump_course_ids', **kwargs)
...@@ -51,11 +60,7 @@ class CommandsTestCase(CommandTestCase): ...@@ -51,11 +60,7 @@ class CommandsTestCase(CommandTestCase):
self.assertEqual(self.loaded_courses, dumped_courses) self.assertEqual(self.loaded_courses, dumped_courses)
def test_dump_course_structure(self): def test_dump_course_structure(self):
dumped_courses = self.call_command('dump_course_ids').split('\n') args = [TEST_COURSE_ID]
self.assertEqual(self.loaded_courses, dumped_courses)
def test_dump_course_structure(self):
args = ['edX/simple/2012_Fall']
kwargs = {'modulestore': 'default'} kwargs = {'modulestore': 'default'}
output = self.call_command('dump_course_structure', *args, **kwargs) output = self.call_command('dump_course_structure', *args, **kwargs)
...@@ -110,3 +115,27 @@ class CommandsTestCase(CommandTestCase): ...@@ -110,3 +115,27 @@ class CommandsTestCase(CommandTestCase):
assert_in('edX-simple-2012_Fall/html/toylab.html', names) assert_in('edX-simple-2012_Fall/html/toylab.html', names)
assert_in('edX-simple-2012_Fall/videosequence/A_simple_sequence.xml', names) assert_in('edX-simple-2012_Fall/videosequence/A_simple_sequence.xml', names)
assert_in('edX-simple-2012_Fall/sequential/Lecture_2.xml', names) assert_in('edX-simple-2012_Fall/sequential/Lecture_2.xml', names)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class CommandsXMLTestCase(CommandsTestBase, ModuleStoreTestCase):
"""
Test case for management commands using the xml modulestore.
"""
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class CommandsMongoTestCase(CommandsTestBase, ModuleStoreTestCase):
"""
Test case for management commands using the mongo modulestore.
"""
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class CommandsMixedTestCase(CommandsTestBase, ModuleStoreTestCase):
"""
Test case for management commands. Using the mixed modulestore.
"""
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