diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py index 554e3c5fc2..fe9056b09a 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py @@ -13,6 +13,7 @@ and then for each combination of modulestores, performing the sequence: """ from contextlib import contextmanager, nested import itertools +import os from path import path import random from shutil import rmtree @@ -314,14 +315,15 @@ class MixedModulestoreBuilder(StoreBuilderBase): # Split stores all asset metadata in the structure collection. return store.db_connection.structures - MIXED_MODULESTORE_BOTH_SETUP = MixedModulestoreBuilder([ ('draft', MongoModulestoreBuilder()), ('split', VersioningModulestoreBuilder()) ]) +DRAFT_MODULESTORE_SETUP = MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]) +SPLIT_MODULESTORE_SETUP = MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]) MIXED_MODULESTORE_SETUPS = ( - MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]), - MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]), + DRAFT_MODULESTORE_SETUP, + SPLIT_MODULESTORE_SETUP, ) MIXED_MS_SETUPS_SHORT = ( 'mixed_mongo', @@ -347,6 +349,8 @@ COURSE_DATA_NAMES = ( 'split_test_module_draft', ) +EXPORTED_COURSE_DIR_NAME = 'exported_source_course' + @ddt.ddt @attr('mongo') @@ -397,14 +401,14 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase): source_content, source_course_key, self.export_dir, - 'exported_source_course', + EXPORTED_COURSE_DIR_NAME, ) import_course_from_xml( dest_store, 'test_user', self.export_dir, - source_dirs=['exported_source_course'], + source_dirs=[EXPORTED_COURSE_DIR_NAME], static_content_store=dest_content, target_id=dest_course_key, raise_on_failure=True, @@ -448,3 +452,58 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase): dest_store, dest_course_key, ) + + def test_split_course_export_import(self): + # Construct the contentstore for storing the first import + with MongoContentstoreBuilder().build() as source_content: + # Construct the modulestore for storing the first import (using the previously created contentstore) + with SPLIT_MODULESTORE_SETUP.build(contentstore=source_content) as source_store: + # Construct the contentstore for storing the second import + with MongoContentstoreBuilder().build() as dest_content: + # Construct the modulestore for storing the second import (using the second contentstore) + with SPLIT_MODULESTORE_SETUP.build(contentstore=dest_content) as dest_store: + source_course_key = source_store.make_course_key('a', 'source', '2015_Fall') + dest_course_key = dest_store.make_course_key('a', 'dest', '2015_Fall') + + import_course_from_xml( + source_store, + 'test_user', + TEST_DATA_DIR, + source_dirs=['split_course_with_static_tabs'], + static_content_store=source_content, + target_id=source_course_key, + raise_on_failure=True, + create_if_not_present=True, + ) + + export_course_to_xml( + source_store, + source_content, + source_course_key, + self.export_dir, + EXPORTED_COURSE_DIR_NAME, + ) + + source_course = source_store.get_course(source_course_key, depth=None, lazy=False) + + self.assertEqual(source_course.url_name, 'course') + + export_dir_path = path(self.export_dir) + policy_dir = export_dir_path / 'exported_source_course' / 'policies' / source_course.url_name + policy_path = policy_dir / 'policy.json' + self.assertTrue(os.path.exists(policy_path)) + + import_course_from_xml( + dest_store, + 'test_user', + self.export_dir, + source_dirs=[EXPORTED_COURSE_DIR_NAME], + static_content_store=dest_content, + target_id=dest_course_key, + raise_on_failure=True, + create_if_not_present=True, + ) + + dest_course = dest_store.get_course(dest_course_key, depth=None, lazy=False) + + self.assertEqual(dest_course.url_name, 'course') diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py index f62d0e3075..a3d95a9b78 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py @@ -264,8 +264,14 @@ class CourseExportManager(ExportManager): 'about', 'about', '.html' ) + course_policy_dir_name = courselike.location.run + if courselike.url_name != courselike.location.run and courselike.url_name == 'course': + # Use url_name for split mongo because course_run is not used when loading policies. + course_policy_dir_name = courselike.url_name + + course_run_policy_dir = policies_dir.makeopendir(course_policy_dir_name) + # export the grading policy - course_run_policy_dir = policies_dir.makeopendir(courselike.location.run) with course_run_policy_dir.open('grading_policy.json', 'w') as grading_policy: grading_policy.write(dumps(courselike.grading_policy, cls=EdxJSONEncoder, sort_keys=True, indent=4)) diff --git a/common/test/data/split_course_with_static_tabs/course.xml b/common/test/data/split_course_with_static_tabs/course.xml new file mode 100644 index 0000000000..86b14ddc4e --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/course.xml @@ -0,0 +1,3 @@ + + + diff --git a/common/test/data/split_course_with_static_tabs/course/course.xml b/common/test/data/split_course_with_static_tabs/course/course.xml new file mode 100644 index 0000000000..c92973175c --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/course/course.xml @@ -0,0 +1,2 @@ + + diff --git a/common/test/data/split_course_with_static_tabs/policies/assets.json b/common/test/data/split_course_with_static_tabs/policies/assets.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/policies/assets.json @@ -0,0 +1 @@ +{} diff --git a/common/test/data/split_course_with_static_tabs/policies/course/grading_policy.json b/common/test/data/split_course_with_static_tabs/policies/course/grading_policy.json new file mode 100644 index 0000000000..2095bb70f7 --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/policies/course/grading_policy.json @@ -0,0 +1 @@ +{"GRADER": [{"short_label": "HW", "min_count": 12, "type": "Homework", "drop_count": 2, "weight": 0.15}, {"min_count": 12, "type": "Lab", "drop_count": 2, "weight": 0.15}, {"short_label": "Midterm", "min_count": 1, "type": "Midterm Exam", "drop_count": 0, "weight": 0.3}, {"short_label": "Final", "min_count": 1, "type": "Final Exam", "drop_count": 0, "weight": 0.4}], "GRADE_CUTOFFS": {"Pass": 0.5}} diff --git a/common/test/data/split_course_with_static_tabs/policies/course/policy.json b/common/test/data/split_course_with_static_tabs/policies/course/policy.json new file mode 100644 index 0000000000..c0f93501ce --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/policies/course/policy.json @@ -0,0 +1 @@ +{"course/3111": {"tabs": [{"type": "courseware", "name": "Courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "textbooks", "name": "Textbooks"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}, {"name": "Test Page", "type": "static_tab", "url_slug": "test_page.html"}], "advanced_modules": ["split_test"], "display_name": "split export test", "user_partitions": [{"description": "Experiment 1", "version": 1, "id": 0, "groups": [{"version": 1, "id": 0, "name": "group 0"}, {"version": 1, "id": 1, "name": "group 1"}], "name": "Experiment 0,1"}, {"description": "Experiment 2", "version": 1, "id": 1, "groups": [{"version": 1, "id": 0, "name": "group A"}, {"version": 1, "id": 1, "name": "group B"}, {"version": 1, "id": 2, "name": "group C"}], "name": "Experiment A,B,C"}], "discussion_topics": {"General": {"id": "i4x-foo-1111-course-3111"}}}} diff --git a/common/test/data/split_course_with_static_tabs/tabs/test_page.html b/common/test/data/split_course_with_static_tabs/tabs/test_page.html new file mode 100644 index 0000000000..6cb87b8787 --- /dev/null +++ b/common/test/data/split_course_with_static_tabs/tabs/test_page.html @@ -0,0 +1 @@ +

Add the content you want students to see on this page.

diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py index 44fb69415e..deaeff8c61 100644 --- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py +++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py @@ -44,6 +44,7 @@ class CommandsTestBase(ModuleStoreTestCase): """ __test__ = False + url_name = '2012_Fall' def setUp(self): super(CommandsTestBase, self).setUp() @@ -198,7 +199,7 @@ class CommandsTestBase(ModuleStoreTestCase): assert_in = self.assertIn assert_in('edX-simple-2012_Fall', names) - assert_in('edX-simple-2012_Fall/policies/2012_Fall/policy.json', names) + assert_in('edX-simple-2012_Fall/policies/{}/policy.json'.format(self.url_name), 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/sequential/Lecture_2.xml', names) @@ -229,3 +230,4 @@ class CommandSplitMongoTestCase(CommandsTestBase): """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE __test__ = True + url_name = 'course'