diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py index bf834458fb..4d0006d10c 100644 --- a/cms/djangoapps/contentstore/course_info_model.py +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -28,16 +28,15 @@ from xmodule.html_module import CourseInfoModule log = logging.getLogger(__name__) -def get_course_updates(location, provided_id): +def get_course_updates(location, provided_id, user_id): """ Retrieve the relevant course_info updates and unpack into the model which the client expects: [{id : index, date : string, content : html string}] """ try: - course_updates = modulestore('direct').get_item(location) + course_updates = modulestore().get_item(location) except ItemNotFoundError: - modulestore('direct').create_and_save_xmodule(location) - course_updates = modulestore('direct').get_item(location) + course_updates = modulestore().create_and_save_xmodule(location, user_id) course_update_items = get_course_update_items(course_updates, provided_id) return _get_visible_update(course_update_items) @@ -50,10 +49,9 @@ def update_course_updates(location, update, passed_id=None, user=None): into the html structure. """ try: - course_updates = modulestore('direct').get_item(location) + course_updates = modulestore().get_item(location) except ItemNotFoundError: - modulestore('direct').create_and_save_xmodule(location) - course_updates = modulestore('direct').get_item(location) + course_updates = modulestore().create_and_save_xmodule(location, user.id) course_update_items = list(reversed(get_course_update_items(course_updates))) @@ -135,7 +133,7 @@ def delete_course_update(location, update, passed_id, user): return HttpResponseBadRequest() try: - course_updates = modulestore('direct').get_item(location) + course_updates = modulestore().get_item(location) except ItemNotFoundError: return HttpResponseBadRequest() @@ -239,6 +237,6 @@ def save_course_update_items(location, course_updates, course_update_items, user course_updates.data = _get_html(course_update_items) # update db record - modulestore('direct').update_item(course_updates, user.id) + modulestore().update_item(course_updates, user.id) return course_updates diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index 77cef035b5..a66f28d536 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -1,8 +1,8 @@ # pylint: disable=C0111 from lettuce import world, step -from contentstore.utils import get_modulestore from selenium.webdriver.common.keys import Keys +from xmodule.modulestore.django import modulestore VIDEO_BUTTONS = { 'CC': '.hide-subtitles', @@ -137,9 +137,9 @@ def xml_only_video(step): world.wait(1) course = world.scenario_dict['COURSE'] - store = get_modulestore(course.location) + store = modulestore() - parent_location = store.get_items(course.id, category='vertical', revision='draft')[0].location + parent_location = store.get_items(course.id, category='vertical')[0].location youtube_id = 'ABCDEFG' world.scenario_dict['YOUTUBE_ID'] = youtube_id diff --git a/cms/djangoapps/contentstore/git_export_utils.py b/cms/djangoapps/contentstore/git_export_utils.py index 8cb938af75..db370112eb 100644 --- a/cms/djangoapps/contentstore/git_export_utils.py +++ b/cms/djangoapps/contentstore/git_export_utils.py @@ -128,8 +128,8 @@ def export_to_git(course_id, repo, user='', rdir=None): root_dir = os.path.dirname(rdirp) course_dir = os.path.splitext(os.path.basename(rdirp))[0] try: - export_to_xml(modulestore('direct'), contentstore(), course_id, - root_dir, course_dir, modulestore()) + export_to_xml(modulestore(), contentstore(), course_id, + root_dir, course_dir) except (EnvironmentError, AttributeError): log.exception('Failed export to xml') raise GitExportError(GitExportError.XML_EXPORT_FAIL) diff --git a/cms/djangoapps/contentstore/management/commands/clone_course.py b/cms/djangoapps/contentstore/management/commands/clone_course.py index 1f98299420..5c961a9767 100644 --- a/cms/djangoapps/contentstore/management/commands/clone_course.py +++ b/cms/djangoapps/contentstore/management/commands/clone_course.py @@ -4,6 +4,7 @@ Script for cloning a course from django.core.management.base import BaseCommand, CommandError from xmodule.modulestore.store_utilities import clone_course from xmodule.modulestore.django import modulestore +from xmodule.modulestore.mixed import store_bulk_write_operations_on_course from xmodule.contentstore.django import contentstore from student.roles import CourseInstructorRole, CourseStaffRole from opaque_keys.edx.keys import CourseKey @@ -35,19 +36,18 @@ class Command(BaseCommand): source_course_id = self.course_key_from_arg(args[0]) dest_course_id = self.course_key_from_arg(args[1]) - mstore = modulestore('direct') + mstore = modulestore() cstore = contentstore() - mstore.ignore_write_events_on_courses.add(dest_course_id) - print("Cloning course {0} to {1}".format(source_course_id, dest_course_id)) - if clone_course(mstore, cstore, source_course_id, dest_course_id): - print("copying User permissions...") - # purposely avoids auth.add_user b/c it doesn't have a caller to authorize - CourseInstructorRole(dest_course_id).add_users( - *CourseInstructorRole(source_course_id).users_with_role() - ) - CourseStaffRole(dest_course_id).add_users( - *CourseStaffRole(source_course_id).users_with_role() - ) + with store_bulk_write_operations_on_course(mstore, dest_course_id): + if clone_course(mstore, cstore, source_course_id, dest_course_id, None): + print("copying User permissions...") + # purposely avoids auth.add_user b/c it doesn't have a caller to authorize + CourseInstructorRole(dest_course_id).add_users( + *CourseInstructorRole(source_course_id).users_with_role() + ) + CourseStaffRole(dest_course_id).add_users( + *CourseStaffRole(source_course_id).users_with_role() + ) diff --git a/cms/djangoapps/contentstore/management/commands/course_id_clash.py b/cms/djangoapps/contentstore/management/commands/course_id_clash.py index 0f0f76aae5..d8ca0128f5 100644 --- a/cms/djangoapps/contentstore/management/commands/course_id_clash.py +++ b/cms/djangoapps/contentstore/management/commands/course_id_clash.py @@ -3,6 +3,7 @@ Script for finding all courses whose org/name pairs == other courses when ignori """ from django.core.management.base import BaseCommand from xmodule.modulestore.django import modulestore +from xmodule.modulestore import MONGO_MODULESTORE_TYPE # @@ -10,12 +11,12 @@ from xmodule.modulestore.django import modulestore # class Command(BaseCommand): """ - Script for finding all courses whose org/name pairs == other courses when ignoring case + Script for finding all courses in the Mongo Modulestore whose org/name pairs == other courses when ignoring case """ - help = 'List all courses ids which may collide when ignoring case' + help = 'List all courses ids in the Mongo Modulestore which may collide when ignoring case' def handle(self, *args, **options): - mstore = modulestore() + mstore = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE) # pylint: disable=protected-access if hasattr(mstore, 'collection'): map_fn = ''' function () { diff --git a/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py b/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py index c28e752c84..9466471fbc 100644 --- a/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py +++ b/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py @@ -22,7 +22,7 @@ class Command(BaseCommand): course_ids = [course_key] else: - course_ids = [course.id for course in modulestore('direct').get_courses()] + course_ids = [course.id for course in modulestore().get_courses()] if query_yes_no("Emptying trashcan. Confirm?", default="no"): empty_asset_trashcan(course_ids) diff --git a/cms/djangoapps/contentstore/management/commands/export.py b/cms/djangoapps/contentstore/management/commands/export.py index c2408080cb..e91d3bb355 100644 --- a/cms/djangoapps/contentstore/management/commands/export.py +++ b/cms/djangoapps/contentstore/management/commands/export.py @@ -35,4 +35,4 @@ class Command(BaseCommand): root_dir = os.path.dirname(output_path) course_dir = os.path.splitext(os.path.basename(output_path))[0] - export_to_xml(modulestore('direct'), contentstore(), course_key, root_dir, course_dir, modulestore()) + export_to_xml(modulestore(), contentstore(), course_key, root_dir, course_dir) diff --git a/cms/djangoapps/contentstore/management/commands/export_all_courses.py b/cms/djangoapps/contentstore/management/commands/export_all_courses.py index b9b05cacb8..a2ff5a82b0 100644 --- a/cms/djangoapps/contentstore/management/commands/export_all_courses.py +++ b/cms/djangoapps/contentstore/management/commands/export_all_courses.py @@ -19,7 +19,7 @@ class Command(BaseCommand): output_path = args[0] cs = contentstore() - ms = modulestore('direct') + ms = modulestore() root_dir = output_path courses = ms.get_courses() @@ -35,7 +35,7 @@ class Command(BaseCommand): if 1: try: course_dir = course_id.replace('/', '...') - export_to_xml(ms, cs, course_id, root_dir, course_dir, modulestore()) + export_to_xml(ms, cs, course_id, root_dir, course_dir) except Exception as err: print("="*30 + "> Oops, failed to export %s" % course_id) print("Error:") diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 915987cd64..e0e1ed0c5d 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -37,15 +37,10 @@ class Command(BaseCommand): data=data_dir, courses=course_dirs, dis=do_import_static)) - try: - mstore = modulestore('direct') - except KeyError: - self.stdout.write('Unable to load direct modulestore, trying ' - 'default\n') - mstore = modulestore('default') + mstore = modulestore() _, course_items = import_from_xml( - mstore, data_dir, course_dirs, load_error_modules=False, + mstore, "**replace_user**", data_dir, course_dirs, load_error_modules=False, static_content_store=contentstore(), verbose=True, do_import_static=do_import_static, create_new_course_if_not_present=True, diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_import.py b/cms/djangoapps/contentstore/management/commands/tests/test_import.py index a051dd1e9d..da9e875719 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_import.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_import.py @@ -8,16 +8,13 @@ import shutil import tempfile from django.core.management import call_command -from django.test.utils import override_settings -from contentstore.tests.modulestore_config import TEST_MODULESTORE from django_comment_common.utils import are_permissions_roles_seeded from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from opaque_keys.edx.locations import SlashSeparatedCourseKey -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestImport(ModuleStoreTestCase): """ Unit tests for importing a course from command line diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py index 70aa4e370c..bf237310b6 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py @@ -5,9 +5,8 @@ import unittest from django.contrib.auth.models import User from django.core.management import CommandError, call_command -from django.test.utils import override_settings from contentstore.management.commands.migrate_to_split import Command -from contentstore.tests.modulestore_config import TEST_MODULESTORE +from xmodule.modulestore import SPLIT_MONGO_MODULESTORE_TYPE, REVISION_OPTION_PUBLISHED_ONLY from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.django import modulestore, clear_existing_modulestores @@ -45,7 +44,6 @@ class TestArgParsing(unittest.TestCase): @unittest.skip("Not fixing split mongo until we land this long branch") -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestMigrateToSplit(ModuleStoreTestCase): """ Unit tests for migrating a course from old mongo to split mongo @@ -58,7 +56,7 @@ class TestMigrateToSplit(ModuleStoreTestCase): password = 'foo' self.user = User.objects.create_user(uname, email, password) self.course = CourseFactory() - self.addCleanup(ModuleStoreTestCase.drop_mongo_collections, 'split') + self.addCleanup(ModuleStoreTestCase.drop_mongo_collections, SPLIT_MONGO_MODULESTORE_TYPE) self.addCleanup(clear_existing_modulestores) def test_user_email(self): @@ -86,6 +84,6 @@ class TestMigrateToSplit(ModuleStoreTestCase): str(self.user.id), "org.dept+name.run", ) - locator = CourseLocator(org="org.dept", offering="name.run", branch="published") + locator = CourseLocator(org="org.dept", offering="name.run", branch=REVISION_OPTION_PUBLISHED_ONLY) course_from_split = modulestore('split').get_course(locator) self.assertIsNotNone(course_from_split) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py index c7e66bccea..3eacdaf672 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py @@ -7,9 +7,7 @@ from mock import patch from django.contrib.auth.models import User from django.core.management import CommandError, call_command -from django.test.utils import override_settings from contentstore.management.commands.rollback_split_course import Command -from contentstore.tests.modulestore_config import TEST_MODULESTORE from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.persistent_factories import PersistentCourseFactory from xmodule.modulestore.tests.factories import CourseFactory @@ -39,7 +37,6 @@ class TestArgParsing(unittest.TestCase): @unittest.skip("Not fixing split mongo until we land opaque-keys 0.9") -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestRollbackSplitCourseNoOldMongo(ModuleStoreTestCase): """ Unit tests for rolling back a split-mongo course from command line, @@ -58,7 +55,6 @@ class TestRollbackSplitCourseNoOldMongo(ModuleStoreTestCase): @unittest.skip("Not fixing split mongo until we land opaque-keys 0.9") -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestRollbackSplitCourseNoSplitMongo(ModuleStoreTestCase): """ Unit tests for rolling back a split-mongo course from command line, @@ -77,7 +73,6 @@ class TestRollbackSplitCourseNoSplitMongo(ModuleStoreTestCase): @unittest.skip("Not fixing split mongo until we land opaque-keys 0.9") -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestRollbackSplitCourse(ModuleStoreTestCase): """ Unit tests for rolling back a split-mongo course from command line diff --git a/cms/djangoapps/contentstore/tests/modulestore_config.py b/cms/djangoapps/contentstore/tests/modulestore_config.py deleted file mode 100644 index 234fa66f9f..0000000000 --- a/cms/djangoapps/contentstore/tests/modulestore_config.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Define test configuration for modulestores. -""" - -from xmodule.modulestore.tests.django_utils import studio_store_config -from django.conf import settings - -TEST_MODULESTORE = studio_store_config(settings.TEST_ROOT / "data") diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index e19e4776dc..d8010e3d06 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -20,8 +20,6 @@ from django.contrib.auth.models import User from django.test import TestCase from django.test.utils import override_settings -from contentstore.utils import get_modulestore -from contentstore.tests.modulestore_config import TEST_MODULESTORE from contentstore.tests.utils import parse_json, AjaxEnabledTestClient from contentstore.views.component import ADVANCED_COMPONENT_TYPES @@ -29,7 +27,11 @@ from xmodule.contentstore.content import StaticContent from xmodule.contentstore.django import contentstore, _CONTENTSTORE from xmodule.contentstore.utils import restore_asset_from_trashcan, empty_asset_trashcan from xmodule.exceptions import NotFoundError, InvalidVersionError -from xmodule.modulestore import mongo +from xmodule.modulestore import ( + mongo, MONGO_MODULESTORE_TYPE, PublishState, + REVISION_OPTION_PUBLISHED_ONLY, REVISION_OPTION_DRAFT_ONLY, KEY_REVISION_PUBLISHED, BRANCH_PUBLISHED_ONLY +) +from xmodule.modulestore.mixed import store_branch_setting from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.inheritance import own_metadata @@ -74,16 +76,12 @@ def get_url(handler_name, key_value, key_name='usage_key_string', kwargs=None): return reverse_url(handler_name, key_name, key_value, kwargs) -@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, MODULESTORE=TEST_MODULESTORE) -class ContentStoreToyCourseTest(ModuleStoreTestCase): +@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) +class ContentStoreTestCase(ModuleStoreTestCase): """ - Tests that rely on the toy courses. - TODO: refactor using CourseFactory so they do not. + Base class for Content Store Test Cases """ def setUp(self): - - settings.MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') - settings.MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') uname = 'testuser' email = 'test+courses@edx.org' password = 'foo' @@ -103,6 +101,12 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.client = AjaxEnabledTestClient() self.client.login(username=uname, password=password) + +class ContentStoreToyCourseTest(ContentStoreTestCase): + """ + Tests that rely on the toy courses. + TODO: refactor using CourseFactory so they do not. + """ def tearDown(self): MongoClient().drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db']) _CONTENTSTORE.clear() @@ -119,8 +123,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): exactly the same -- for example, 'video' in component_types should cause 'Video' to be present. """ - store = modulestore('direct') - _, course_items = import_from_xml(store, 'common/test/data/', ['simple']) + store = modulestore() + _, course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['simple']) course = course_items[0] course.advanced_modules = component_types store.update_item(course, self.user.id) @@ -147,8 +151,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.check_components_on_page(['word_cloud'], ['Word cloud']) def test_malformed_edit_unit_request(self): - store = modulestore('direct') - _, course_items = import_from_xml(store, 'common/test/data/', ['simple']) + store = modulestore() + _, course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['simple']) # just pick one vertical usage_key = course_items[0].id.make_usage_key('vertical', None) @@ -158,7 +162,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): _test_no_locations(self, resp, status_code=400) def check_edit_unit(self, test_course_name): - _, course_items = import_from_xml(modulestore('direct'), 'common/test/data/', [test_course_name]) + _, course_items = import_from_xml(modulestore(), self.user.id, 'common/test/data/', [test_course_name]) items = modulestore().get_items(course_items[0].id, category='vertical') self._check_verticals(items) @@ -191,25 +195,22 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): Unfortunately, None = published for the revision field, so get_items() would return both draft and non-draft copies. ''' - direct_store = modulestore('direct') - draft_store = modulestore('draft') - _, course_items = import_from_xml(direct_store, 'common/test/data/', ['simple']) + store = modulestore() + _, course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['simple']) course_key = course_items[0].id html_usage_key = course_key.make_usage_key('html', 'test_html') - html_module_from_draft_store = draft_store.get_item(html_usage_key) - draft_store.convert_to_draft(html_module_from_draft_store.location) + html_module_from_draft_store = store.get_item(html_usage_key) + store.convert_to_draft(html_module_from_draft_store.location, self.user.id) # Query get_items() and find the html item. This should just return back a single item (not 2). - - direct_store_items = direct_store.get_items(course_key) + direct_store_items = store.get_items(course_key, revision=REVISION_OPTION_PUBLISHED_ONLY) html_items_from_direct_store = [item for item in direct_store_items if (item.location == html_usage_key)] self.assertEqual(len(html_items_from_direct_store), 1) self.assertFalse(getattr(html_items_from_direct_store[0], 'is_draft', False)) - # Fetch from the draft store. Note that even though we pass - # None in the revision field, the draft store will replace that with 'draft'. - draft_store_items = draft_store.get_items(course_key) + # Fetch from the draft store. + draft_store_items = store.get_items(course_key, revision=REVISION_OPTION_DRAFT_ONLY) html_items_from_draft_store = [item for item in draft_store_items if (item.location == html_usage_key)] self.assertEqual(len(html_items_from_draft_store), 1) self.assertTrue(getattr(html_items_from_draft_store[0], 'is_draft', False)) @@ -221,9 +222,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): module as 'own-metadata' when publishing. Also verifies the metadata inheritance is properly computed ''' - store = modulestore('direct') - draft_store = modulestore('draft') - import_from_xml(store, 'common/test/data/', ['simple']) + draft_store = modulestore() + import_from_xml(draft_store, self.user.id, 'common/test/data/', ['simple']) course_key = SlashSeparatedCourseKey('edX', 'simple', '2012_Fall') html_usage_key = course_key.make_usage_key('html', 'test_html') @@ -233,7 +233,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertEqual(html_module.graceperiod, course.graceperiod) self.assertNotIn('graceperiod', own_metadata(html_module)) - draft_store.convert_to_draft(html_module.location) + draft_store.convert_to_draft(html_module.location, self.user.id) # refetch to check metadata html_module = draft_store.get_item(html_usage_key) @@ -242,7 +242,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertNotIn('graceperiod', own_metadata(html_module)) # publish module - draft_store.publish(html_module.location, 0) + draft_store.publish(html_module.location, self.user.id) # refetch to check metadata html_module = draft_store.get_item(html_usage_key) @@ -251,7 +251,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertNotIn('graceperiod', own_metadata(html_module)) # put back in draft and change metadata and see if it's now marked as 'own_metadata' - draft_store.convert_to_draft(html_module.location) + draft_store.convert_to_draft(html_module.location, self.user.id) html_module = draft_store.get_item(html_usage_key) new_graceperiod = timedelta(hours=1) @@ -273,45 +273,46 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertEqual(html_module.graceperiod, new_graceperiod) # republish - draft_store.publish(html_module.location, 0) + draft_store.publish(html_module.location, self.user.id) # and re-read and verify 'own-metadata' - draft_store.convert_to_draft(html_module.location) + draft_store.convert_to_draft(html_module.location, self.user.id) html_module = draft_store.get_item(html_usage_key) self.assertIn('graceperiod', own_metadata(html_module)) self.assertEqual(html_module.graceperiod, new_graceperiod) def test_get_depth_with_drafts(self): - import_from_xml(modulestore('direct'), 'common/test/data/', ['simple']) + store = modulestore() + import_from_xml(store, self.user.id, 'common/test/data/', ['simple']) course_key = SlashSeparatedCourseKey('edX', 'simple', '2012_Fall') - course = modulestore('draft').get_course(course_key) + course = store.get_course(course_key) # make sure no draft items have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 0) problem_usage_key = course_key.make_usage_key('problem', 'ps01-simple') - problem = modulestore('draft').get_item(problem_usage_key) + problem = store.get_item(problem_usage_key) # put into draft - modulestore('draft').convert_to_draft(problem.location) + store.convert_to_draft(problem.location, self.user.id) # make sure we can query that item and verify that it is a draft - draft_problem = modulestore('draft').get_item(problem_usage_key) + draft_problem = store.get_item(problem_usage_key) self.assertTrue(getattr(draft_problem, 'is_draft', False)) # now requery with depth - course = modulestore('draft').get_course(course_key) + course = store.get_course(course_key) # make sure just one draft item have been returned num_drafts = self._get_draft_counts(course) self.assertEqual(num_drafts, 1) def test_no_static_link_rewrites_on_import(self): - module_store = modulestore('direct') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course = course_items[0] handouts_usage_key = course.id.make_usage_key('course_info', 'handouts') @@ -330,14 +331,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """).strip() - module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course = module_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')) self.assertGreater(len(course.textbooks), 0) def test_import_polls(self): - module_store = modulestore('direct') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_key = course_items[0].id items = module_store.get_items(course_key, category='poll_question') @@ -356,8 +357,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """ Tests the ajax callback to render an XModule """ - direct_store = modulestore('direct') - _, course_items = import_from_xml(direct_store, 'common/test/data/', ['toy']) + direct_store = modulestore() + _, course_items = import_from_xml(direct_store, self.user.id, 'common/test/data/', ['toy']) usage_key = course_items[0].id.make_usage_key('vertical', 'vertical_test') # also try a custom response which will trigger the 'is this course in whitelist' logic @@ -375,32 +376,32 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertContains(resp, 'edX+toy+2012_Fall+poll_question+T1_changemind_poll_foo_2') def test_delete(self): - direct_store = modulestore('direct') + store = modulestore() course = CourseFactory.create(org='edX', course='999', display_name='Robot Super Course') chapterloc = ItemFactory.create(parent_location=course.location, display_name="Chapter").location ItemFactory.create(parent_location=chapterloc, category='sequential', display_name="Sequential") sequential_key = course.id.make_usage_key('sequential', 'Sequential') - sequential = direct_store.get_item(sequential_key) + sequential = store.get_item(sequential_key) chapter_key = course.id.make_usage_key('chapter', 'Chapter') - chapter = direct_store.get_item(chapter_key) + chapter = store.get_item(chapter_key) # make sure the parent points to the child object which is to be deleted self.assertTrue(sequential.location in chapter.children) - self.client.delete(get_url('xblock_handler', sequential_key), {'recurse': True, 'all_versions': True}) + self.client.delete(get_url('xblock_handler', sequential_key)) found = False try: - direct_store.get_item(sequential_key) + store.get_item(sequential_key) found = True except ItemNotFoundError: pass self.assertFalse(found) - chapter = direct_store.get_item(chapter_key) + chapter = store.get_item(chapter_key) # make sure the parent no longer points to the child object which was deleted self.assertFalse(sequential.location in chapter.children) @@ -410,8 +411,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html while there is a base definition in /about/effort.html ''' - module_store = modulestore('direct') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_key = course_items[0].id effort = module_store.get_item(course_key.make_usage_key('about', 'effort')) self.assertEqual(effort.data, '6 hours') @@ -426,8 +427,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ''' content_store = contentstore() - module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store, verbose=True) + module_store = modulestore() + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store, verbose=True) course = module_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')) @@ -515,8 +516,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): content_store = contentstore() trash_store = contentstore('trashcan') - module_store = modulestore('direct') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) # look up original (and thumbnail) in content store, should be there after import location = AssetLocation.from_deprecated_string('/c4x/edX/toy/asset/sample_static.txt') @@ -547,9 +548,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): Test that course info updates are imported and exported with all content fields ('data', 'items') """ content_store = contentstore() - module_store = modulestore('direct') + module_store = modulestore() data_dir = "common/test/data/" - import_from_xml(module_store, data_dir, ['course_info_updates'], + import_from_xml(module_store, self.user.id, data_dir, ['course_info_updates'], static_content_store=content_store, verbose=True) course_id = SlashSeparatedCourseKey('edX', 'course_info_updates', '2014_T1') @@ -636,25 +637,22 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): 'run': '2013_Spring', } - module_store = modulestore('direct') - draft_store = modulestore('draft') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) source_course_id = course_items[0].id dest_course_id = _get_course_id(course_data) - # get a vertical (and components in it) to put into 'draft' + # get a vertical (and components in it) to put into DRAFT # this is to assert that draft content is also cloned over vertical = module_store.get_item( source_course_id.make_usage_key('vertical', 'vertical_test'), depth=1 ) - draft_store.convert_to_draft(vertical.location) - for child in vertical.get_children(): - draft_store.convert_to_draft(child.location) + module_store.convert_to_draft(vertical.location, self.user.id) - items = module_store.get_items(source_course_id, revision='draft') + items = module_store.get_items(source_course_id) self.assertGreater(len(items), 0) _create_course(self, dest_course_id, course_data) @@ -662,25 +660,27 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): content_store = contentstore() # now do the actual cloning - clone_course(module_store, content_store, source_course_id, dest_course_id) + clone_course(module_store, content_store, source_course_id, dest_course_id, self.user.id) # first assert that all draft content got cloned as well - items = module_store.get_items(source_course_id, revision='draft') - self.assertGreater(len(items), 0) - clone_items = module_store.get_items(dest_course_id, revision='draft') - self.assertGreater(len(clone_items), 0) - self.assertEqual(len(items), len(clone_items)) + draft_items = module_store.get_items(source_course_id, revision=REVISION_OPTION_DRAFT_ONLY) + self.assertGreater(len(draft_items), 0) + draft_clone_items = module_store.get_items(dest_course_id, revision=REVISION_OPTION_DRAFT_ONLY) + self.assertGreater(len(draft_clone_items), 0) + self.assertEqual(len(draft_items), len(draft_clone_items)) # now loop through all the units in the course and verify that the clone can render them, which # means the objects are at least present - items = module_store.get_items(source_course_id, revision=None) + items = module_store.get_items(source_course_id) self.assertGreater(len(items), 0) - clone_items = module_store.get_items(dest_course_id, revision=None) + clone_items = module_store.get_items(dest_course_id) self.assertGreater(len(clone_items), 0) for descriptor in items: source_item = module_store.get_item(descriptor.location) new_loc = descriptor.location.map_into_course(dest_course_id) + if descriptor.location.category == 'course': + new_loc = new_loc.replace(name=new_loc.run) print "Checking {0} should now also be at {1}".format(descriptor.location, new_loc) lookup_item = module_store.get_item(new_loc) @@ -708,10 +708,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): 'run': '2013_Spring' } - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy']) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) source_course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') dest_course_id = _get_course_id(course_data) @@ -732,7 +732,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): _create_course(self, dest_course_id, course_data) # do the actual cloning - clone_course(module_store, content_store, source_course_id, dest_course_id) + clone_course(module_store, content_store, source_course_id, dest_course_id, self.user.id) # make sure that any non-portable links are rewritten during cloning html_module = module_store.get_item(dest_course_id.make_usage_key('html', 'nonportable')) @@ -740,16 +740,16 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertIn('/asset/foo.jpg', html_module.data) def test_illegal_draft_crud_ops(self): - draft_store = modulestore('draft') + draft_store = modulestore() course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') location = course.id.make_usage_key('chapter', 'neuvo') # Ensure draft mongo store does not create drafts for things that shouldn't be draft - newobject = draft_store.create_and_save_xmodule(location) + newobject = draft_store.create_and_save_xmodule(location, self.user.id) self.assertFalse(getattr(newobject, 'is_draft', False)) with self.assertRaises(InvalidVersionError): - draft_store.convert_to_draft(location) + draft_store.convert_to_draft(location, self.user.id) chapter = draft_store.get_item(location) chapter.data = 'chapter data' @@ -758,7 +758,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertFalse(getattr(newobject, 'is_draft', False)) with self.assertRaises(InvalidVersionError): - draft_store.unpublish(location) + draft_store.unpublish(location, self.user.id) def test_bad_contentstore_request(self): resp = self.client.get_html('http://localhost:8001/c4x/CDX/123123/asset/&images_circuits_Lab7Solution2.png') @@ -766,10 +766,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): _test_no_locations(self, resp, 400) def test_rewrite_nonportable_links_on_import(self): - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) # first check a static asset link course_key = SlashSeparatedCourseKey('edX', 'toy', 'run') @@ -787,21 +787,17 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): This test will import a course, make a draft item, and delete it. This will also assert that the draft content is also deleted """ - module_store = modulestore('direct') - + module_store = modulestore() content_store = contentstore() - draft_store = modulestore('draft') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store) + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) course_id = course_items[0].id - # get a vertical (and components in it) to put into 'draft' + # get a vertical (and components in it) to put into DRAFT vertical = module_store.get_item(course_id.make_usage_key('vertical', 'vertical_test'), depth=1) - draft_store.convert_to_draft(vertical.location) - for child in vertical.get_children(): - draft_store.convert_to_draft(child.location) + module_store.convert_to_draft(vertical.location, self.user.id) # delete the course delete_course(module_store, content_store, course_id, commit=True) @@ -827,53 +823,58 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertTrue(filesystem.exists(item.location.name + filename_suffix)) @mock.patch('xmodule.course_module.requests.get') - def test_export_course(self, mock_get): + def test_export_course_roundtrip(self, mock_get): mock_get.return_value.text = dedent(""" """).strip() - module_store = modulestore('direct') - draft_store = modulestore('draft') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # get a vertical (and components in it) to copy into an orphan sub dag vertical = module_store.get_item(course_id.make_usage_key('vertical', 'vertical_test'), depth=1) # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. - vertical.location = mongo.draft.as_draft(vertical.location.replace(name='no_references')) + vertical.location = vertical.location.replace(name='no_references') - draft_store.update_item(vertical, allow_not_found=True) - orphan_vertical = draft_store.get_item(vertical.location) + module_store.update_item(vertical, self.user.id, allow_not_found=True) + orphan_vertical = module_store.get_item(vertical.location) self.assertEqual(orphan_vertical.location.name, 'no_references') - # get the original vertical (and components in it) to put into 'draft' + # get the original vertical (and components in it) to put into DRAFT vertical = module_store.get_item(course_id.make_usage_key('vertical', 'vertical_test'), depth=1) self.assertEqual(len(orphan_vertical.children), len(vertical.children)) - draft_store.convert_to_draft(vertical.location) - for child in vertical.get_children(): - draft_store.convert_to_draft(child.location) + draft_vertical = module_store.convert_to_draft(vertical.location, self.user.id) + self.assertEqual(module_store.compute_publish_state(draft_vertical), PublishState.draft) root_dir = path(mkdtemp_clean()) # now create a new/different private (draft only) vertical vertical.location = mongo.draft.as_draft(course_id.make_usage_key('vertical', 'a_private_vertical')) - draft_store.update_item(vertical, allow_not_found=True) - private_vertical = draft_store.get_item(vertical.location) + vertical = module_store.create_and_save_xmodule(vertical.location, self.user.id) + self.assertEqual(module_store.compute_publish_state(vertical), PublishState.private) + private_vertical = module_store.get_item(vertical.location) vertical = None # blank out b/c i destructively manipulated its location 2 lines above - # add the new private to list of children + # now create a new/different published (no draft) vertical + public_vertical_location = course_id.make_usage_key('vertical', 'a_published_vertical') + module_store.create_and_save_xmodule(public_vertical_location, self.user.id) + public_vertical = module_store.publish(public_vertical_location, self.user.id) + self.assertEqual(module_store.compute_publish_state(public_vertical), PublishState.public) + + # add the new private and new public to list of children sequential = module_store.get_item(course_id.make_usage_key('sequential', 'vertical_sequential')) - private_location_no_draft = private_vertical.location.replace(revision=None) + private_location_no_draft = private_vertical.location.replace(revision=KEY_REVISION_PUBLISHED) sequential.children.append(private_location_no_draft) + sequential.children.append(public_vertical_location) module_store.update_item(sequential, self.user.id) # read back the sequential, to make sure we have a pointer to sequential = module_store.get_item(course_id.make_usage_key('sequential', 'vertical_sequential')) - self.assertIn(private_location_no_draft, sequential.children) locked_asset_key = self._lock_an_asset(content_store, course_id) @@ -884,7 +885,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): print 'Exporting to tempdir = {0}'.format(root_dir) # export out to a tempdir - export_to_xml(module_store, content_store, course_id, root_dir, 'test_export', draft_modulestore=draft_store) + export_to_xml(module_store, content_store, course_id, root_dir, 'test_export') # check for static tabs self.verify_content_existence(module_store, root_dir, course_id, 'tabs', 'static_tab', '.html') @@ -915,44 +916,51 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): delete_course(module_store, content_store, course_id, commit=True) # reimport over old course self.check_import( - module_store, root_dir, draft_store, content_store, course_id, + module_store, root_dir, content_store, course_id, locked_asset_key, locked_asset_attrs ) # import to different course id self.check_import( - module_store, root_dir, draft_store, content_store, SlashSeparatedCourseKey('anotherX', 'anotherToy', 'Someday'), + module_store, root_dir, content_store, SlashSeparatedCourseKey('anotherX', 'anotherToy', 'Someday'), locked_asset_key, locked_asset_attrs ) shutil.rmtree(root_dir) - def check_import(self, module_store, root_dir, draft_store, content_store, course_id, + def check_import(self, module_store, root_dir, content_store, course_id, locked_asset_key, locked_asset_attrs): # reimport import_from_xml( module_store, + self.user.id, root_dir, ['test_export'], - draft_store=draft_store, static_content_store=content_store, target_course_id=course_id, ) - items = module_store.get_items(course_id, category='vertical') + items = module_store.get_items(course_id, category='vertical', revision=REVISION_OPTION_PUBLISHED_ONLY) self._check_verticals(items) - # verify that we have the content in the draft store as well - vertical = draft_store.get_item( - course_id.make_usage_key('vertical', 'vertical_test'), - depth=1 - ) + def verify_item_publish_state(item, publish_state): + if publish_state in (PublishState.private, PublishState.draft): + self.assertTrue(getattr(item, 'is_draft', False)) + else: + self.assertFalse(getattr(item, 'is_draft', False)) + self.assertEqual(module_store.compute_publish_state(item), publish_state) - self.assertTrue(getattr(vertical, 'is_draft', False)) + def get_and_verify_item_publish_state(item_type, item_name, publish_state): + item = module_store.get_item(course_id.make_usage_key(item_type, item_name)) + verify_item_publish_state(item, publish_state) + return item + + # verify that the draft vertical is draft + vertical = get_and_verify_item_publish_state('vertical', 'vertical_test', PublishState.draft) self.assertNotIn('index_in_children_list', vertical.xml_attributes) self.assertNotIn('parent_sequential_url', vertical.xml_attributes) for child in vertical.get_children(): - self.assertTrue(getattr(child, 'is_draft', False)) + verify_item_publish_state(child, PublishState.draft) self.assertNotIn('index_in_children_list', child.xml_attributes) if hasattr(child, 'data'): self.assertNotIn('index_in_children_list', child.data) @@ -960,19 +968,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): if hasattr(child, 'data'): self.assertNotIn('parent_sequential_url', child.data) - # make sure that we don't have a sequential that is in draft mode - sequential = draft_store.get_item( - course_id.make_usage_key('sequential', 'vertical_sequential') - ) - - self.assertFalse(getattr(sequential, 'is_draft', False)) + # make sure that we don't have a sequential that is not in draft mode + get_and_verify_item_publish_state('sequential', 'vertical_sequential', PublishState.public) # verify that we have the private vertical - test_private_vertical = draft_store.get_item( - course_id.make_usage_key('vertical', 'a_private_vertical') - ) + get_and_verify_item_publish_state('vertical', 'a_private_vertical', PublishState.private) - self.assertTrue(getattr(test_private_vertical, 'is_draft', False)) + # verify that we have the public vertical + get_and_verify_item_publish_state('vertical', 'a_published_vertical', PublishState.public) # make sure the textbook survived the export/import course = module_store.get_course(course_id) @@ -990,11 +993,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertEqual(value, new_attrs[key]) def test_export_course_with_metadata_only_video(self): - module_store = modulestore('direct') - draft_store = modulestore('draft') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy']) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # create a new video module and add it as a child to a vertical @@ -1013,7 +1015,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): print 'Exporting to tempdir = {0}'.format(root_dir) # export out to a tempdir - export_to_xml(module_store, content_store, course_id, root_dir, 'test_export', draft_modulestore=draft_store) + export_to_xml(module_store, content_store, course_id, root_dir, 'test_export') shutil.rmtree(root_dir) @@ -1021,11 +1023,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """ Similar to `test_export_course_with_metadata_only_video`. """ - module_store = modulestore('direct') - draft_store = modulestore('draft') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['word_cloud']) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['word_cloud']) course_id = SlashSeparatedCourseKey('HarvardX', 'ER22x', '2013_Spring') verticals = module_store.get_items(course_id, category='vertical') @@ -1041,7 +1042,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): print 'Exporting to tempdir = {0}'.format(root_dir) # export out to a tempdir - export_to_xml(module_store, content_store, course_id, root_dir, 'test_export', draft_modulestore=draft_store) + export_to_xml(module_store, content_store, course_id, root_dir, 'test_export') shutil.rmtree(root_dir) @@ -1050,11 +1051,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): Test that an empty `data` field is preserved through export/import. """ - module_store = modulestore('direct') - draft_store = modulestore('draft') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy']) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') verticals = module_store.get_items(course_id, category='vertical') @@ -1070,10 +1070,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # Export the course root_dir = path(mkdtemp_clean()) - export_to_xml(module_store, content_store, course_id, root_dir, 'test_roundtrip', draft_modulestore=draft_store) + export_to_xml(module_store, content_store, course_id, root_dir, 'test_roundtrip') # Reimport and get the video back - import_from_xml(module_store, root_dir) + import_from_xml(module_store, self.user.id, root_dir) imported_word_cloud = module_store.get_item(course_id.make_usage_key('word_cloud', 'untitled')) # It should now contain empty data @@ -1083,10 +1083,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """ Test that a course which has HTML that has style formatting is preserved in export/import """ - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() - import_from_xml(module_store, 'common/test/data/', ['toy']) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') @@ -1095,7 +1095,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): export_to_xml(module_store, content_store, course_id, root_dir, 'test_roundtrip') # Reimport and get the video back - import_from_xml(module_store, root_dir) + import_from_xml(module_store, self.user.id, root_dir) # get the sample HTML with styling information html_module = module_store.get_item(course_id.make_usage_key('html', 'with_styling')) @@ -1106,10 +1106,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertIn('', html_module.data) def test_course_handouts_rewrites(self): - module_store = modulestore('direct') + module_store = modulestore() # import a test course - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_id = course_items[0].id handouts_location = course_id.make_usage_key('course_info', 'handouts') @@ -1124,35 +1124,43 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertContains(resp, '/c4x/edX/toy/asset/handouts_sample_handout.txt') def test_prefetch_children(self): - module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['toy']) + mongo_store = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE) + import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['toy']) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') - wrapper = MongoCollectionFindWrapper(module_store.collection.find) - module_store.collection.find = wrapper.find - print module_store.metadata_inheritance_cache_subsystem - print module_store.request_cache - course = module_store.get_course(course_id, depth=2) + wrapper = MongoCollectionFindWrapper(mongo_store.collection.find) + mongo_store.collection.find = wrapper.find - # make sure we haven't done too many round trips to DB - # note we say 3 round trips here for 1) the course, and 2 & 3) for the chapters and sequentials - # Because we're querying from the top of the tree, we cache information needed for inheritance, - # so we don't need to make an extra query to compute it. - self.assertEqual(wrapper.counter, 3) + # set the branch to 'publish' in order to prevent extra lookups of draft versions + with store_branch_setting(mongo_store, BRANCH_PUBLISHED_ONLY): + course = mongo_store.get_course(course_id, depth=2) - # make sure we pre-fetched a known sequential which should be at depth=2 - self.assertTrue(course_id.make_usage_key('sequential', 'vertical_sequential') in course.system.module_data) + # make sure we haven't done too many round trips to DB + # note we say 3 round trips here for 1) the course, and 2 & 3) for the chapters and sequentials + # Because we're querying from the top of the tree, we cache information needed for inheritance, + # so we don't need to make an extra query to compute it. + self.assertEqual(wrapper.counter, 3) + + # make sure we pre-fetched a known sequential which should be at depth=2 + self.assertTrue(course_id.make_usage_key('sequential', 'vertical_sequential') in course.system.module_data) + + # make sure we don't have a specific vertical which should be at depth=3 + self.assertFalse(course_id.make_usage_key('vertical', 'vertical_test') in course.system.module_data) + + # Now, test with the branch set to draft. We should have one extra round trip call to check for + # the existence of the draft versions + wrapper.counter = 0 + mongo_store.get_course(course_id, depth=2) + self.assertEqual(wrapper.counter, 4) - # make sure we don't have a specific vertical which should be at depth=3 - self.assertFalse(course_id.make_usage_key('vertical', 'vertical_test') in course.system.module_data) def test_export_course_without_content_store(self): - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() # Create toy course - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course_id = course_items[0].id root_dir = path(mkdtemp_clean()) @@ -1165,8 +1173,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): delete_course(module_store, content_store, course_id, commit=True) import_from_xml( - module_store, root_dir, ['test_export_no_content_store'], - draft_store=None, + module_store, self.user.id, root_dir, ['test_export_no_content_store'], static_content_store=None, target_course_id=course_id ) @@ -1190,8 +1197,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): _test_no_locations(self, resp) -@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, MODULESTORE=TEST_MODULESTORE) -class ContentStoreTest(ModuleStoreTestCase): +class ContentStoreTest(ContentStoreTestCase): """ Tests for the CMS ContentStore application. """ @@ -1536,7 +1542,7 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertEqual(resp.status_code, 200) payload = parse_json(resp) problem_loc = UsageKey.from_string(payload['locator']) - problem = get_modulestore(problem_loc).get_item(problem_loc) + problem = modulestore().get_item(problem_loc) # should be a CapaDescriptor self.assertIsInstance(problem, CapaDescriptor, "New problem is not a CapaDescriptor") context = problem.get_context() @@ -1557,7 +1563,7 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertEqual(resp.status_code, 200) _test_no_locations(self, resp) - _, course_items = import_from_xml(modulestore('direct'), 'common/test/data/', ['simple']) + _, course_items = import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['simple']) course_key = course_items[0].id resp = self._show_course_overview(course_key) @@ -1609,11 +1615,11 @@ class ContentStoreTest(ModuleStoreTestCase): delete_item(category='chapter', name='chapter_2') def test_import_into_new_course_id(self): - module_store = modulestore('direct') + module_store = modulestore() target_course_id = _get_course_id(self.course_data) _create_course(self, target_course_id, self.course_data) - import_from_xml(module_store, 'common/test/data/', ['toy'], target_course_id=target_course_id) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id) modules = module_store.get_items(target_course_id) @@ -1634,7 +1640,7 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertEqual(course_module.pdf_textbooks[0]["chapters"][1]["url"], '/static/Chapter2.pdf') def test_import_into_new_course_id_wiki_slug_renamespacing(self): - module_store = modulestore('direct') + module_store = modulestore() # If reimporting into the same course do not change the wiki_slug. target_course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') @@ -1650,7 +1656,7 @@ class ContentStoreTest(ModuleStoreTestCase): course_module.save() # Import a course with wiki_slug == location.course - import_from_xml(module_store, 'common/test/data/', ['toy'], target_course_id=target_course_id) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id) course_module = module_store.get_course(target_course_id) self.assertEquals(course_module.wiki_slug, 'toy') @@ -1665,18 +1671,18 @@ class ContentStoreTest(ModuleStoreTestCase): _create_course(self, target_course_id, course_data) # Import a course with wiki_slug == location.course - import_from_xml(module_store, 'common/test/data/', ['toy'], target_course_id=target_course_id) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id) course_module = module_store.get_course(target_course_id) self.assertEquals(course_module.wiki_slug, 'MITx.999.2013_Spring') # Now try importing a course with wiki_slug == '{0}.{1}.{2}'.format(location.org, location.course, location.run) - import_from_xml(module_store, 'common/test/data/', ['two_toys'], target_course_id=target_course_id) + import_from_xml(module_store, self.user.id, 'common/test/data/', ['two_toys'], target_course_id=target_course_id) course_module = module_store.get_course(target_course_id) self.assertEquals(course_module.wiki_slug, 'MITx.999.2013_Spring') def test_import_metadata_with_attempts_empty_string(self): - module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['simple']) + module_store = modulestore() + import_from_xml(module_store, self.user.id, 'common/test/data/', ['simple']) did_load_item = False try: course_key = SlashSeparatedCourseKey('edX', 'simple', 'problem') @@ -1690,20 +1696,20 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertTrue(did_load_item) def test_forum_id_generation(self): - module_store = modulestore('direct') + module_store = modulestore() course = CourseFactory.create(org='edX', course='999', display_name='Robot Super Course') new_component_location = course.id.make_usage_key('discussion', 'new_component') # crate a new module and add it as a child to a vertical - module_store.create_and_save_xmodule(new_component_location) + module_store.create_and_save_xmodule(new_component_location, self.user.id) new_discussion_item = module_store.get_item(new_component_location) self.assertNotEquals(new_discussion_item.discussion_id, '$$GUID$$') def test_metadata_inheritance(self): - module_store = modulestore('direct') - _, course_items = import_from_xml(module_store, 'common/test/data/', ['toy']) + module_store = modulestore() + _, course_items = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy']) course = course_items[0] verticals = module_store.get_items(course.id, category='vertical') @@ -1718,13 +1724,13 @@ class ContentStoreTest(ModuleStoreTestCase): new_component_location = course.id.make_usage_key('html', 'new_component') # crate a new module and add it as a child to a vertical - module_store.create_and_save_xmodule(new_component_location) + new_object = module_store.create_xmodule(new_component_location) + module_store.update_item(new_object, self.user.id, allow_not_found=True) parent = verticals[0] parent.children.append(new_component_location) module_store.update_item(parent, self.user.id) # flush the cache - module_store.refresh_cached_metadata_inheritance_tree(new_component_location.course_key) new_module = module_store.get_item(new_component_location) # check for grace period definition which should be defined at the course level @@ -1741,7 +1747,6 @@ class ContentStoreTest(ModuleStoreTestCase): module_store.update_item(new_module, self.user.id) # flush the cache and refetch - module_store.refresh_cached_metadata_inheritance_tree(new_component_location.course_key) new_module = module_store.get_item(new_component_location) self.assertEqual(timedelta(1), new_module.graceperiod) @@ -1759,7 +1764,7 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertGreaterEqual(len(course.checklists), 4) # by fetching - module_store = modulestore('direct') + module_store = modulestore() fetched_course = module_store.get_item(course.location) fetched_item = module_store.get_item(vertical.location) self.assertIsNotNone(fetched_course.start) @@ -1771,13 +1776,14 @@ class ContentStoreTest(ModuleStoreTestCase): def test_image_import(self): """Test backwards compatibilty of course image.""" - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() # Use conditional_and_poll, as it's got an image already import_from_xml( module_store, + self.user.id, 'common/test/data/', ['conditional_and_poll'], static_content_store=content_store @@ -1805,15 +1811,16 @@ class ContentStoreTest(ModuleStoreTestCase): course_key = _get_course_id(self.course_data) _create_course(self, course_key, self.course_data) - course_module = modulestore('direct').get_course(course_key) + course_module = modulestore().get_course(course_key) self.assertEquals(course_module.wiki_slug, 'MITx.999.2013_Spring') -@override_settings(MODULESTORE=TEST_MODULESTORE) -class MetadataSaveTestCase(ModuleStoreTestCase): +class MetadataSaveTestCase(ContentStoreTestCase): """Test that metadata is correctly cached and decached.""" def setUp(self): + super(MetadataSaveTestCase, self).setUp() + course = CourseFactory.create( org='edX', course='999', display_name='Robot Super Course') @@ -1857,8 +1864,8 @@ class MetadataSaveTestCase(ModuleStoreTestCase): delattr(self.video_descriptor, field_name) self.assertNotIn('html5_sources', own_metadata(self.video_descriptor)) - get_modulestore(location).update_item(self.video_descriptor, '**replace_user**') - module = get_modulestore(location).get_item(location) + modulestore().update_item(self.video_descriptor, self.user.id) + module = modulestore().get_item(location) self.assertNotIn('html5_sources', own_metadata(module)) diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py index b0b378a21d..387b2678e5 100644 --- a/cms/djangoapps/contentstore/tests/test_course_listing.py +++ b/cms/djangoapps/contentstore/tests/test_course_listing.py @@ -16,7 +16,8 @@ from contentstore.tests.utils import AjaxEnabledTestClient from student.tests.factories import UserFactory from student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff, OrgStaffRole, OrgInstructorRole from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls +from xmodule.modulestore import MONGO_MODULESTORE_TYPE from opaque_keys.edx.locations import SlashSeparatedCourseKey from xmodule.modulestore.django import modulestore from xmodule.error_module import ErrorDescriptor @@ -197,6 +198,14 @@ class TestCourseListing(ModuleStoreTestCase): self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed) self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed) + # Now count the db queries + store = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE) + with check_mongo_calls(store.collection, USER_COURSES_COUNT): + courses_list = _accessible_courses_list_from_groups(self.request) + + with check_mongo_calls(store.collection, 1): + courses_list = _accessible_courses_list(self.request) + def test_get_course_list_with_same_course_id(self): """ Test getting courses with same id but with different name case. Then try to delete one of them and @@ -253,18 +262,20 @@ class TestCourseListing(ModuleStoreTestCase): Create good courses, courses that won't load, and deleted courses which still have roles. Test course listing. """ + store = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE) + course_location = SlashSeparatedCourseKey('testOrg', 'testCourse', 'RunBabyRun') self._create_course_with_access_groups(course_location, self.user) course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun') self._create_course_with_access_groups(course_location, self.user) - modulestore().delete_course(course_location) + store.delete_course(course_location) course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun') course = self._create_course_with_access_groups(course_location, self.user) - course_db_record = modulestore()._find_one(course.location) + course_db_record = store._find_one(course.location) course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki" }) - modulestore().collection.update( + store.collection.update( {'_id': course.location.to_deprecated_son()}, {'$set': { 'metadata.tabs': course_db_record['metadata']['tabs'], diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 978209c526..8192b5fb70 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -11,7 +11,7 @@ from django.test.utils import override_settings from models.settings.course_details import (CourseDetails, CourseSettingsEncoder) from models.settings.course_grading import CourseGradingModel -from contentstore.utils import get_modulestore, EXTRA_TAB_PANELS, reverse_course_url, reverse_usage_url +from contentstore.utils import EXTRA_TAB_PANELS, reverse_course_url, reverse_usage_url from xmodule.modulestore.tests.factories import CourseFactory from models.settings.course_metadata import CourseMetadata @@ -335,7 +335,7 @@ class CourseGradingTest(CourseTestCase): def test_update_section_grader_type(self): # Get the descriptor and the section_grader_type and assert they are the default values - descriptor = get_modulestore(self.course.location).get_item(self.course.location) + descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('notgraded', section_grader_type['graderType']) @@ -344,7 +344,7 @@ class CourseGradingTest(CourseTestCase): # Change the default grader type to Homework, which should also mark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user) - descriptor = get_modulestore(self.course.location).get_item(self.course.location) + descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('Homework', section_grader_type['graderType']) @@ -353,7 +353,7 @@ class CourseGradingTest(CourseTestCase): # Change the grader type back to notgraded, which should also unmark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user) - descriptor = get_modulestore(self.course.location).get_item(self.course.location) + descriptor = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('notgraded', section_grader_type['graderType']) @@ -413,8 +413,7 @@ class CourseGradingTest(CourseTestCase): Populate the course, grab a section, get the url for the assignment type access """ self.populate_course() - sequential_usage_key = self.course.id.make_usage_key("sequential", None) - sections = get_modulestore(self.course.id).get_items(sequential_usage_key) + sections = modulestore().get_items(self.course.id, category="sequential") # see if test makes sense self.assertGreater(len(sections), 0, "No sections found") section = sections[0] # just take the first one @@ -470,7 +469,7 @@ class CourseMetadataEditingTest(CourseTestCase): ) self.update_check(test_model) # try fresh fetch to ensure persistence - fresh = modulestore('direct').get_course(self.course.id) + fresh = modulestore().get_course(self.course.id) test_model = CourseMetadata.fetch(fresh) self.update_check(test_model) # now change some of the existing metadata diff --git a/cms/djangoapps/contentstore/tests/test_crud.py b/cms/djangoapps/contentstore/tests/test_crud.py index 58b0291d49..9431016b6c 100644 --- a/cms/djangoapps/contentstore/tests/test_crud.py +++ b/cms/djangoapps/contentstore/tests/test_crud.py @@ -1,9 +1,11 @@ import unittest from xmodule import templates +from xmodule.modulestore import SPLIT_MONGO_MODULESTORE_TYPE, BRANCH_NAME_DRAFT from xmodule.modulestore.tests import persistent_factories from xmodule.course_module import CourseDescriptor -from xmodule.modulestore.django import modulestore, loc_mapper, clear_existing_modulestores +from xmodule.modulestore.django import modulestore, clear_existing_modulestores, _MIXED_MODULESTORE, \ + loc_mapper, _loc_singleton from xmodule.seq_module import SequenceDescriptor from xmodule.capa_module import CapaDescriptor from opaque_keys.edx.locator import BlockUsageLocator, LocalId @@ -19,8 +21,9 @@ class TemplateTests(unittest.TestCase): def setUp(self): clear_existing_modulestores() # redundant w/ cleanup but someone was getting errors - self.addCleanup(ModuleStoreTestCase.drop_mongo_collections, 'split') + self.addCleanup(ModuleStoreTestCase.drop_mongo_collections, SPLIT_MONGO_MODULESTORE_TYPE) self.addCleanup(clear_existing_modulestores) + self.split_store = modulestore()._get_modulestore_by_type(SPLIT_MONGO_MODULESTORE_TYPE) def test_get_templates(self): found = templates.all_templates() @@ -59,7 +62,7 @@ class TemplateTests(unittest.TestCase): ) self.assertIsInstance(test_course, CourseDescriptor) self.assertEqual(test_course.display_name, 'fun test course') - index_info = modulestore('split').get_course_index_info(test_course.id) + index_info = self.split_store.get_course_index_info(test_course.id) self.assertEqual(index_info['org'], 'testx') self.assertEqual(index_info['offering'], 'tempcourse') @@ -67,7 +70,7 @@ class TemplateTests(unittest.TestCase): parent_location=test_course.location) self.assertIsInstance(test_chapter, SequenceDescriptor) # refetch parent which should now point to child - test_course = modulestore('split').get_course(test_course.id.version_agnostic()) + test_course = self.split_store.get_course(test_course.id.version_agnostic()) self.assertIn(test_chapter.location, test_course.children) with self.assertRaises(DuplicateCourseError): @@ -85,7 +88,7 @@ class TemplateTests(unittest.TestCase): display_name='fun test course', user_id='testbot' ) - test_chapter = modulestore('split').create_xblock( + test_chapter = self.split_store.create_xblock( test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course ) self.assertIsInstance(test_chapter, SequenceDescriptor) @@ -94,7 +97,7 @@ class TemplateTests(unittest.TestCase): # test w/ a definition (e.g., a problem) test_def_content = 'boo' - test_problem = modulestore('split').create_xblock( + test_problem = self.split_store.create_xblock( test_course.system, 'problem', {'data': test_def_content}, parent_xblock=test_chapter ) self.assertIsInstance(test_problem, CapaDescriptor) @@ -111,13 +114,13 @@ class TemplateTests(unittest.TestCase): offering='tempcourse', org='testx', display_name='fun test course', user_id='testbot' ) - test_chapter = modulestore('split').create_xblock( + test_chapter = self.split_store.create_xblock( test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course ) self.assertEqual(test_chapter.display_name, 'chapter n') test_def_content = 'boo' # create child - new_block = modulestore('split').create_xblock( + new_block = self.split_store.create_xblock( test_course.system, 'problem', fields={ @@ -131,7 +134,7 @@ class TemplateTests(unittest.TestCase): # better to pass in persisted parent over the subdag so # subdag gets the parent pointer (otherwise 2 ops, persist dag, update parent children, # persist parent - persisted_course = modulestore('split').persist_xblock_dag(test_course, 'testbot') + persisted_course = self.split_store.persist_xblock_dag(test_course, 'testbot') self.assertEqual(len(persisted_course.children), 1) persisted_chapter = persisted_course.get_children()[0] self.assertEqual(persisted_chapter.category, 'chapter') @@ -142,7 +145,7 @@ class TemplateTests(unittest.TestCase): self.assertEqual(persisted_problem.data, test_def_content) # update it persisted_problem.display_name = 'altered problem' - persisted_problem = modulestore('split').persist_xblock_dag(persisted_problem, 'testbot') + persisted_problem = self.split_store.persist_xblock_dag(persisted_problem, 'testbot') self.assertEqual(persisted_problem.display_name, 'altered problem') def test_delete_course(self): @@ -153,17 +156,17 @@ class TemplateTests(unittest.TestCase): persistent_factories.ItemFactory.create(display_name='chapter 1', parent_location=test_course.location) - id_locator = test_course.id.for_branch('draft') + id_locator = test_course.id.for_branch(BRANCH_NAME_DRAFT) guid_locator = test_course.location.course_agnostic() # verify it can be retrieved by id - self.assertIsInstance(modulestore('split').get_course(id_locator), CourseDescriptor) + self.assertIsInstance(self.split_store.get_course(id_locator), CourseDescriptor) # and by guid - self.assertIsInstance(modulestore('split').get_item(guid_locator), CourseDescriptor) - modulestore('split').delete_course(id_locator) + self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor) + self.split_store.delete_course(id_locator) # test can no longer retrieve by id - self.assertRaises(ItemNotFoundError, modulestore('split').get_course, id_locator) + self.assertRaises(ItemNotFoundError, self.split_store.get_course, id_locator) # but can by guid - self.assertIsInstance(modulestore('split').get_item(guid_locator), CourseDescriptor) + self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor) def test_block_generations(self): """ @@ -184,11 +187,11 @@ class TemplateTests(unittest.TestCase): ) first_problem.max_attempts = 3 first_problem.save() # decache the above into the kvs - updated_problem = modulestore('split').update_item(first_problem, '**replace_user**') + updated_problem = self.split_store.update_item(first_problem, '**replace_user**') self.assertIsNotNone(updated_problem.previous_version) self.assertEqual(updated_problem.previous_version, first_problem.update_version) self.assertNotEqual(updated_problem.update_version, first_problem.update_version) - updated_loc = modulestore('split').delete_item(updated_problem.location, 'testbot', delete_children=True) + updated_loc = self.split_store.delete_item(updated_problem.location, '**replace_user**', 'testbot') second_problem = persistent_factories.ItemFactory.create( display_name='problem 2', @@ -200,45 +203,59 @@ class TemplateTests(unittest.TestCase): ) # course root only updated 2x - version_history = modulestore('split').get_block_generations(test_course.location) + version_history = self.split_store.get_block_generations(test_course.location) self.assertEqual(version_history.locator.version_guid, test_course.location.version_guid) self.assertEqual(len(version_history.children), 1) self.assertEqual(version_history.children[0].children, []) self.assertEqual(version_history.children[0].locator.version_guid, chapter.location.version_guid) # sub changed on add, add problem, delete problem, add problem in strict linear seq - version_history = modulestore('split').get_block_generations(sub.location) + version_history = self.split_store.get_block_generations(sub.location) self.assertEqual(len(version_history.children), 1) self.assertEqual(len(version_history.children[0].children), 1) self.assertEqual(len(version_history.children[0].children[0].children), 1) self.assertEqual(len(version_history.children[0].children[0].children[0].children), 0) # first and second problem may show as same usage_id; so, need to ensure their histories are right - version_history = modulestore('split').get_block_generations(updated_problem.location) + version_history = self.split_store.get_block_generations(updated_problem.location) self.assertEqual(version_history.locator.version_guid, first_problem.location.version_guid) self.assertEqual(len(version_history.children), 1) # updated max_attempts self.assertEqual(len(version_history.children[0].children), 0) - version_history = modulestore('split').get_block_generations(second_problem.location) + version_history = self.split_store.get_block_generations(second_problem.location) self.assertNotEqual(version_history.locator.version_guid, first_problem.location.version_guid) + +class SplitAndLocMapperTests(unittest.TestCase): + """ + Test injection of loc_mapper into Split + """ def test_split_inject_loc_mapper(self): """ - Test that creating a loc_mapper causes it to automatically attach to the split mongo store + Test loc_mapper created before split """ + # ensure modulestore is not instantiated + self.assertIsNone(_MIXED_MODULESTORE) + # instantiate location mapper before split mapper = loc_mapper() - # split must inject the location mapper itself since the mapper existed before it did - self.assertEqual(modulestore('split').loc_mapper, mapper) + + # instantiate mixed modulestore and thus split + split_store = modulestore()._get_modulestore_by_type(SPLIT_MONGO_MODULESTORE_TYPE) + + # split must inject the same location mapper object since the mapper existed before it did + self.assertEqual(split_store.loc_mapper, mapper) def test_loc_inject_into_split(self): """ - Test that creating a loc_mapper causes it to automatically attach to the split mongo store + Test split created before loc_mapper """ - # force instantiation of split modulestore before there's a location mapper and verify - # it has no pointer to loc mapper - self.assertIsNone(modulestore('split').loc_mapper) - # force instantiation of location mapper which must inject itself into the split - mapper = loc_mapper() - self.assertEqual(modulestore('split').loc_mapper, mapper) + # ensure loc_mapper is not instantiated + self.assertIsNone(_loc_singleton) + # instantiate split before location mapper + split_store = modulestore()._get_modulestore_by_type(SPLIT_MONGO_MODULESTORE_TYPE) + + # split must have instantiated loc_mapper + mapper = loc_mapper() + self.assertEqual(split_store.loc_mapper, mapper) diff --git a/cms/djangoapps/contentstore/tests/test_export_git.py b/cms/djangoapps/contentstore/tests/test_export_git.py index 28cad509c8..b023470bbf 100644 --- a/cms/djangoapps/contentstore/tests/test_export_git.py +++ b/cms/djangoapps/contentstore/tests/test_export_git.py @@ -16,7 +16,7 @@ from .utils import CourseTestCase import contentstore.git_export_utils as git_export_utils from xmodule.contentstore.django import _CONTENTSTORE from xmodule.modulestore.django import modulestore -from contentstore.utils import get_modulestore, reverse_course_url +from contentstore.utils import reverse_course_url TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex @@ -66,7 +66,7 @@ class TestExportGit(CourseTestCase): Test failed course export response. """ self.course_module.giturl = 'foobar' - get_modulestore(self.course_module.location).update_item(self.course_module) + modulestore().update_item(self.course_module, '**replace_user**') response = self.client.get('{}?action=push'.format(self.test_url)) self.assertIn('Export Failed:', response.content) @@ -76,7 +76,7 @@ class TestExportGit(CourseTestCase): Regression test for making sure errors are properly stringified """ self.course_module.giturl = 'foobar' - get_modulestore(self.course_module.location).update_item(self.course_module) + modulestore().update_item(self.course_module, '**replace_user**') response = self.client.get('{}?action=push'.format(self.test_url)) self.assertNotIn('django.utils.functional.__proxy__', response.content) @@ -99,7 +99,7 @@ class TestExportGit(CourseTestCase): self.populate_course() self.course_module.giturl = 'file://{}'.format(bare_repo_dir) - get_modulestore(self.course_module.location).update_item(self.course_module) + modulestore().update_item(self.course_module, '**replace_user**') response = self.client.get('{}?action=push'.format(self.test_url)) self.assertIn('Export Succeeded', response.content) diff --git a/cms/djangoapps/contentstore/tests/test_i18n.py b/cms/djangoapps/contentstore/tests/test_i18n.py index 04917753b1..d9843cb621 100644 --- a/cms/djangoapps/contentstore/tests/test_i18n.py +++ b/cms/djangoapps/contentstore/tests/test_i18n.py @@ -1,14 +1,11 @@ from unittest import skip from django.contrib.auth.models import User -from django.test.utils import override_settings from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from contentstore.tests.modulestore_config import TEST_MODULESTORE from contentstore.tests.utils import AjaxEnabledTestClient -@override_settings(MODULESTORE=TEST_MODULESTORE) class InternationalizationTest(ModuleStoreTestCase): """ Tests to validate Internationalization. diff --git a/cms/djangoapps/contentstore/tests/test_import.py b/cms/djangoapps/contentstore/tests/test_import.py index bd31e5b585..919c7e04fe 100644 --- a/cms/djangoapps/contentstore/tests/test_import.py +++ b/cms/djangoapps/contentstore/tests/test_import.py @@ -13,13 +13,11 @@ import copy from django.contrib.auth.models import User from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from contentstore.tests.modulestore_config import TEST_MODULESTORE from xmodule.modulestore.django import modulestore from xmodule.contentstore.django import contentstore from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation from xmodule.modulestore.xml_importer import import_from_xml -from xmodule.contentstore.content import StaticContent from xmodule.contentstore.django import _CONTENTSTORE from xmodule.exceptions import NotFoundError @@ -30,7 +28,7 @@ TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex -@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, MODULESTORE=TEST_MODULESTORE) +@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class ContentStoreImportTest(ModuleStoreTestCase): """ Tests that rely on the toy and test_import_course courses. @@ -38,8 +36,6 @@ class ContentStoreImportTest(ModuleStoreTestCase): """ def setUp(self): - settings.MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') - settings.MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') uname = 'testuser' email = 'test+courses@edx.org' password = 'foo' @@ -69,9 +65,10 @@ class ContentStoreImportTest(ModuleStoreTestCase): (for do_import_static=False behavior). ''' content_store = contentstore() - module_store = modulestore('direct') + module_store = modulestore() import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['test_import_course'], static_content_store=content_store, @@ -91,6 +88,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): module_store, __, course = self.load_test_import_course() __, course_items = import_from_xml( module_store, + '**replace_user**', 'common/test/data', ['test_import_course_2'], target_course_id=course.id, @@ -102,10 +100,11 @@ class ContentStoreImportTest(ModuleStoreTestCase): """ # Test that importing course with unicode 'id' and 'display name' doesn't give UnicodeEncodeError """ - module_store = modulestore('direct') + module_store = modulestore() course_id = SlashSeparatedCourseKey(u'Юникода', u'unicode_course', u'échantillon') import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['2014_Uni'], target_course_id=course_id @@ -150,8 +149,8 @@ class ContentStoreImportTest(ModuleStoreTestCase): ''' content_store = contentstore() - module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True) + module_store = modulestore() + import_from_xml(module_store, '**replace_user**', 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True) course = module_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')) @@ -161,8 +160,8 @@ class ContentStoreImportTest(ModuleStoreTestCase): self.assertEqual(count, 0) def test_no_static_link_rewrites_on_import(self): - module_store = modulestore('direct') - _, courses = import_from_xml(module_store, 'common/test/data/', ['toy'], do_import_static=False, verbose=True) + module_store = modulestore() + _, courses = import_from_xml(module_store, '**replace_user**', 'common/test/data/', ['toy'], do_import_static=False, verbose=True) course_key = courses[0].id handouts = module_store.get_item(course_key.make_usage_key('course_info', 'handouts')) @@ -177,10 +176,11 @@ class ContentStoreImportTest(ModuleStoreTestCase): self.assertEqual(course.tabs[2]['name'], 'Syllabus') def test_rewrite_reference_list(self): - module_store = modulestore('direct') + module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['conditional'], target_course_id=target_course_id @@ -206,10 +206,11 @@ class ContentStoreImportTest(ModuleStoreTestCase): ) def test_rewrite_reference(self): - module_store = modulestore('direct') + module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', 'peergrading_copy', 'copy_run') import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['open_ended'], target_course_id=target_course_id @@ -224,10 +225,11 @@ class ContentStoreImportTest(ModuleStoreTestCase): ) def test_rewrite_reference_value_dict(self): - module_store = modulestore('direct') + module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', 'split_test_copy', 'copy_run') import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['split_test_module'], target_course_id=target_course_id diff --git a/cms/djangoapps/contentstore/tests/test_import_draft_order.py b/cms/djangoapps/contentstore/tests/test_import_draft_order.py index 7222eec90d..090bf69921 100644 --- a/cms/djangoapps/contentstore/tests/test_import_draft_order.py +++ b/cms/djangoapps/contentstore/tests/test_import_draft_order.py @@ -1,24 +1,18 @@ -from django.test.utils import override_settings - from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.django import modulestore -from contentstore.tests.modulestore_config import TEST_MODULESTORE - # This test is in the CMS module because the test configuration to use a draft # modulestore is dependent on django. -@override_settings(MODULESTORE=TEST_MODULESTORE) class DraftReorderTestCase(ModuleStoreTestCase): def test_order(self): - store = modulestore('direct') - draft_store = modulestore('default') - _, course_items = import_from_xml(store, 'common/test/data/', ['import_draft_order'], draft_store=draft_store) + store = modulestore() + _, course_items = import_from_xml(store, '**replace_user**', 'common/test/data/', ['import_draft_order']) course_key = course_items[0].id - sequential = draft_store.get_item(course_key.make_usage_key('sequential', '0f4f7649b10141b0bdc9922dcf94515a')) + sequential = store.get_item(course_key.make_usage_key('sequential', '0f4f7649b10141b0bdc9922dcf94515a')) verticals = sequential.children # The order that files are read in from the file system is not guaranteed (cannot rely on @@ -39,7 +33,7 @@ class DraftReorderTestCase(ModuleStoreTestCase): self.assertEqual(course_key.make_usage_key('vertical', 'c'), verticals[6]) # Now also test that the verticals in a second sequential are correct. - sequential = draft_store.get_item(course_key.make_usage_key('sequential', 'secondseq')) + sequential = store.get_item(course_key.make_usage_key('sequential', 'secondseq')) verticals = sequential.children # 'asecond' and 'zsecond' are drafts with 'index_in_children_list' 0 and 2, respectively. # 'secondsubsection' is a public vertical. diff --git a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py index c30ece1568..d87419ca3b 100644 --- a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py +++ b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py @@ -2,8 +2,6 @@ Integration tests for importing courses containing pure XBlocks. """ -from django.test.utils import override_settings - from xblock.core import XBlock from xblock.fields import String @@ -11,7 +9,6 @@ from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.django import modulestore from xmodule.modulestore.mongo.draft import as_draft -from contentstore.tests.modulestore_config import TEST_MODULESTORE class StubXBlock(XBlock): @@ -29,12 +26,10 @@ class StubXBlock(XBlock): test_field = String(default="default") -@override_settings(MODULESTORE=TEST_MODULESTORE) class XBlockImportTest(ModuleStoreTestCase): def setUp(self): - self.store = modulestore('direct') - self.draft_store = modulestore('default') + self.store = modulestore() @XBlock.register_temp_plugin(StubXBlock) def test_import_public(self): @@ -67,8 +62,7 @@ class XBlockImportTest(ModuleStoreTestCase): """ _, courses = import_from_xml( - self.store, 'common/test/data', [course_dir], - draft_store=self.draft_store + self.store, '**replace_user**', 'common/test/data', [course_dir] ) xblock_location = courses[0].id.make_usage_key('stubxblock', 'xblock_test') @@ -81,6 +75,7 @@ class XBlockImportTest(ModuleStoreTestCase): self.assertEqual(xblock.test_field, expected_field_val) if has_draft: - draft_xblock = self.draft_store.get_item(xblock_location) + draft_xblock = self.store.get_item(xblock_location) + self.assertTrue(getattr(draft_xblock, 'is_draft', False)) self.assertTrue(isinstance(draft_xblock, StubXBlock)) self.assertEqual(draft_xblock.test_field, expected_field_val) diff --git a/cms/djangoapps/contentstore/tests/test_orphan.py b/cms/djangoapps/contentstore/tests/test_orphan.py index 037ecb2ad4..3b080aa080 100644 --- a/cms/djangoapps/contentstore/tests/test_orphan.py +++ b/cms/djangoapps/contentstore/tests/test_orphan.py @@ -32,8 +32,10 @@ class TestOrphan(CourseTestCase): def _create_item(self, category, name, data, metadata, parent_category, parent_name, runtime): location = self.course.location.replace(category=category, name=name) - store = modulestore('direct') - store.create_and_save_xmodule(location, data, metadata, runtime) + store = modulestore() + store.create_and_save_xmodule( + location, self.user.id, definition_data=data, metadata=metadata, runtime=runtime + ) if parent_name: # add child to parent in mongo parent_location = self.course.location.replace(category=parent_category, name=parent_name) diff --git a/cms/djangoapps/contentstore/tests/test_permissions.py b/cms/djangoapps/contentstore/tests/test_permissions.py index 082d7e06bc..8398ef57a8 100644 --- a/cms/djangoapps/contentstore/tests/test_permissions.py +++ b/cms/djangoapps/contentstore/tests/test_permissions.py @@ -3,11 +3,9 @@ Test CRUD for authorization. """ import copy -from django.test.utils import override_settings from django.contrib.auth.models import User from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from contentstore.tests.modulestore_config import TEST_MODULESTORE from contentstore.tests.utils import AjaxEnabledTestClient from opaque_keys.edx.locations import SlashSeparatedCourseKey from contentstore.utils import reverse_url, reverse_course_url @@ -16,7 +14,6 @@ from contentstore.views.access import has_course_access from student import auth -@override_settings(MODULESTORE=TEST_MODULESTORE) class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) diff --git a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py index 8f60ea6af0..a739e39263 100644 --- a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py +++ b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py @@ -21,7 +21,6 @@ from xmodule.exceptions import NotFoundError from xmodule.contentstore.django import contentstore, _CONTENTSTORE from xmodule.video_module import transcripts_utils -from contentstore.tests.modulestore_config import TEST_MODULESTORE TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex @@ -76,7 +75,7 @@ class TestGenerateSubs(unittest.TestCase): ) -@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, MODULESTORE=TEST_MODULESTORE) +@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class TestSaveSubsToStore(ModuleStoreTestCase): """Tests for `save_subs_to_store` function.""" @@ -156,7 +155,7 @@ class TestSaveSubsToStore(ModuleStoreTestCase): _CONTENTSTORE.clear() -@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, MODULESTORE=TEST_MODULESTORE) +@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class TestDownloadYoutubeSubs(ModuleStoreTestCase): """Tests for `download_youtube_subs` function.""" diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 39620bd8b8..0de3e01bd5 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -15,14 +15,12 @@ from contentstore.tests.utils import parse_json, user, registration, AjaxEnabled from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from contentstore.tests.test_course_settings import CourseTestCase from xmodule.modulestore.tests.factories import CourseFactory -from contentstore.tests.modulestore_config import TEST_MODULESTORE import datetime from pytz import UTC from freezegun import freeze_time -@override_settings(MODULESTORE=TEST_MODULESTORE) class ContentStoreTestCase(ModuleStoreTestCase): def _login(self, email, password): """ diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index 3d81ca6c7b..062f79a2e0 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -6,12 +6,10 @@ import json from django.contrib.auth.models import User from django.test.client import Client -from django.test.utils import override_settings +from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from contentstore.tests.modulestore_config import TEST_MODULESTORE -from contentstore.utils import get_modulestore from student.models import Registration @@ -58,8 +56,6 @@ class AjaxEnabledTestClient(Client): return self.get(path, data or {}, follow, HTTP_ACCEPT="application/json", **extra) - -@override_settings(MODULESTORE=TEST_MODULESTORE) class CourseTestCase(ModuleStoreTestCase): def setUp(self): """ @@ -91,7 +87,7 @@ class CourseTestCase(ModuleStoreTestCase): number='999', display_name='Robot Super Course', ) - self.store = get_modulestore(self.course.location) + self.store = modulestore() def create_non_staff_authed_user_client(self, authenticate=True): """ diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index f18176b855..e75ddedeb0 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -11,7 +11,7 @@ from django.core.urlresolvers import reverse from xmodule.contentstore.content import StaticContent from xmodule.contentstore.django import contentstore from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore.mixed import store_bulk_write_operations_on_course from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location from xmodule.modulestore.store_utilities import delete_course from student.roles import CourseInstructorRole, CourseStaffRole @@ -30,31 +30,22 @@ def delete_course_and_groups(course_id, commit=False): This deletes the courseware associated with a course_id as well as cleaning update_item the various user table stuff (groups, permissions, etc.) """ - module_store = modulestore('direct') + module_store = modulestore() content_store = contentstore() - module_store.ignore_write_events_on_courses.add(course_id) + with store_bulk_write_operations_on_course(module_store, course_id): + if delete_course(module_store, content_store, course_id, commit): - if delete_course(module_store, content_store, course_id, commit): - - print 'removing User permissions from course....' - # in the django layer, we need to remove all the user permissions groups associated with this course - if commit: - try: - staff_role = CourseStaffRole(course_id) - staff_role.remove_users(*staff_role.users_with_role()) - instructor_role = CourseInstructorRole(course_id) - instructor_role.remove_users(*instructor_role.users_with_role()) - except Exception as err: - log.error("Error in deleting course groups for {0}: {1}".format(course_id, err)) - - -def get_modulestore(category_or_location): - """ - This function no longer does anything more than just calling `modulestore()`. It used - to select 'direct' v 'draft' based on the category. - """ - return modulestore() + print 'removing User permissions from course....' + # in the django layer, we need to remove all the user permissions groups associated with this course + if commit: + try: + staff_role = CourseStaffRole(course_id) + staff_role.remove_users(*staff_role.users_with_role()) + instructor_role = CourseInstructorRole(course_id) + instructor_role.remove_users(*instructor_role.users_with_role()) + except Exception as err: + log.error("Error in deleting course groups for {0}: {1}".format(course_id, err)) def get_lms_link_for_item(location, preview=False): @@ -124,35 +115,18 @@ def course_image_url(course): return path -class PublishState(object): - """ - The publish state for a given xblock-- either 'draft', 'private', or 'public'. - - Currently in CMS, an xblock can only be in 'draft' or 'private' if it is at or below the Unit level. - """ - draft = 'draft' - private = 'private' - public = 'public' - - def compute_publish_state(xblock): """ - Returns whether this xblock is 'draft', 'public', or 'private'. + Returns whether this xblock is draft, public, or private. - 'draft' content is in the process of being edited, but still has a previous - version visible in the LMS - 'public' content is locked and visible in the LMS - 'private' content is editable and not visible in the LMS + Returns: + PublishState.draft - content is in the process of being edited, but still has a previous + version deployed to LMS + PublishState.public - content is locked and deployed to LMS + PublishState.private - content is editable and not deployed to LMS """ - if getattr(xblock, 'is_draft', False): - try: - modulestore('direct').get_item(xblock.location) - return PublishState.draft - except ItemNotFoundError: - return PublishState.private - else: - return PublishState.public + return modulestore().compute_publish_state(xblock) def add_extra_panel_tab(tab_type, course): diff --git a/cms/djangoapps/contentstore/views/checklist.py b/cms/djangoapps/contentstore/views/checklist.py index 1f86a11e71..71c593f8ee 100644 --- a/cms/djangoapps/contentstore/views/checklist.py +++ b/cms/djangoapps/contentstore/views/checklist.py @@ -11,7 +11,7 @@ from django.http import HttpResponseNotFound from django.core.exceptions import PermissionDenied from opaque_keys.edx.keys import CourseKey from xmodule.modulestore.django import modulestore -from contentstore.utils import get_modulestore, reverse_course_url +from contentstore.utils import reverse_course_url from .access import has_course_access from xmodule.course_module import CourseDescriptor @@ -47,7 +47,7 @@ def checklists_handler(request, course_key_string, checklist_index=None): # from the template. if not course_module.checklists: course_module.checklists = CourseDescriptor.checklists.default - get_modulestore(course_module.location).update_item(course_module, request.user.id) + modulestore().update_item(course_module, request.user.id) expanded_checklists = expand_all_action_urls(course_module) if json_request: @@ -76,7 +76,7 @@ def checklists_handler(request, course_key_string, checklist_index=None): # not default course_module.checklists = course_module.checklists course_module.save() - get_modulestore(course_module.location).update_item(course_module, request.user.id) + modulestore().update_item(course_module, request.user.id) expanded_checklist = expand_checklist_action_url(course_module, persisted_checklist) return JsonResponse(localize_checklist_text(expanded_checklist)) else: diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 03de2cbf92..2499ff9123 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -13,6 +13,7 @@ from edxmako.shortcuts import render_to_response from util.date_utils import get_default_time_display from xmodule.modulestore.django import modulestore +from xmodule.modulestore import PublishState from xblock.core import XBlock from xblock.django.request import webob_to_django_response, django_to_webob_request @@ -21,7 +22,7 @@ from xblock.fields import Scope from xblock.plugin import PluginMissingError from xblock.runtime import Mixologist -from contentstore.utils import get_lms_link_for_item, compute_publish_state, PublishState, get_modulestore +from contentstore.utils import get_lms_link_for_item, compute_publish_state from contentstore.views.helpers import get_parent_xblock from models.settings.course_grading import CourseGradingModel @@ -413,7 +414,7 @@ def _get_item_in_course(request, usage_key): raise PermissionDenied() course = modulestore().get_course(course_key) - item = get_modulestore(usage_key).get_item(usage_key, depth=1) + item = modulestore().get_item(usage_key, depth=1) lms_link = get_lms_link_for_item(usage_key) return course, item, lms_link @@ -436,7 +437,7 @@ def component_handler(request, usage_key_string, handler, suffix=''): usage_key = UsageKey.from_string(usage_key_string) - descriptor = get_modulestore(usage_key).get_item(usage_key) + descriptor = modulestore().get_item(usage_key) # Let the module handle the AJAX req = django_to_webob_request(request) @@ -449,6 +450,6 @@ def component_handler(request, usage_key_string, handler, suffix=''): # unintentional update to handle any side effects of handle call; so, request user didn't author # the change - get_modulestore(usage_key).update_item(descriptor, None) + modulestore().update_item(descriptor, None) return webob_to_django_response(resp) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 085db39c8d..8935013e97 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -31,7 +31,6 @@ from contentstore.utils import ( get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab, - get_modulestore, reverse_course_url ) from models.settings.course_details import CourseDetails, CourseSettingsEncoder @@ -165,7 +164,7 @@ def _accessible_courses_list(request): """ List all courses available to the logged in user by iterating through all the courses """ - courses = modulestore('direct').get_courses() + courses = modulestore().get_courses() # filter out courses that we don't have access to def course_filter(course): @@ -202,14 +201,15 @@ def _accessible_courses_list_from_groups(request): if course_key is None: # If the course_access does not have a course_id, it's an org-based role, so we fall back raise AccessListFallback - try: - course = modulestore('direct').get_course(course_key) - except ItemNotFoundError: - # If a user has access to a course that doesn't exist, don't do anything with that course - pass - if course is not None and not isinstance(course, ErrorDescriptor): - # ignore deleted or errored courses - courses_list[course_key] = course + if course_key not in courses_list: + try: + course = modulestore().get_course(course_key) + except ItemNotFoundError: + # If a user has access to a course that doesn't exist, don't do anything with that course + pass + if course is not None and not isinstance(course, ErrorDescriptor): + # ignore deleted or errored courses + courses_list[course_key] = course return courses_list.values() @@ -333,7 +333,7 @@ def create_new_course(request): fields.update(metadata) # Creating the course raises InvalidLocationError if an existing course with this org/name is found - new_course = modulestore('direct').create_course( + new_course = modulestore().create_course( course_key.org, course_key.offering, fields=fields, @@ -440,7 +440,7 @@ def course_info_update_handler(request, course_key_string, provided_id=None): raise PermissionDenied() if request.method == 'GET': - course_updates = get_course_updates(usage_key, provided_id) + course_updates = get_course_updates(usage_key, provided_id, request.user.id) if isinstance(course_updates, dict) and course_updates.get('error'): return JsonResponse(course_updates, course_updates.get('status', 400)) else: @@ -739,7 +739,7 @@ def textbooks_list_handler(request, course_key_string): """ course_key = CourseKey.from_string(course_key_string) course = _get_course_module(course_key, request.user) - store = get_modulestore(course.location) + store = modulestore() if not "application/json" in request.META.get('HTTP_ACCEPT', 'text/html'): # return HTML page @@ -814,7 +814,7 @@ def textbooks_detail_handler(request, course_key_string, textbook_id): """ course_key = CourseKey.from_string(course_key_string) course_module = _get_course_module(course_key, request.user) - store = get_modulestore(course_module.location) + store = modulestore() matching_id = [tb for tb in course_module.pdf_textbooks if unicode(tb.get("id")) == unicode(textbook_id)] if matching_id: diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index f2550adc4e..f24d4828fd 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -39,34 +39,16 @@ def render_from_lms(template_name, dictionary, context=None, namespace='main'): return render_to_string(template_name, dictionary, context, namespace="lms." + namespace) -def _xmodule_recurse(item, action, ignore_exception=()): - """ - Recursively apply provided action on item and its children - - ignore_exception (Exception Object): A optional argument; when passed ignores the corresponding - exception raised during xmodule recursion, - """ - for child in item.get_children(): - _xmodule_recurse(child, action, ignore_exception) - - try: - return action(item) - except ignore_exception: - return - - def get_parent_xblock(xblock): """ Returns the xblock that is the parent of the specified xblock, or None if it has no parent. """ locator = xblock.location - parent_locations = modulestore().get_parent_locations(locator,) + parent_location = modulestore().get_parent_location(locator) - if len(parent_locations) == 0: + if parent_location is None: return None - elif len(parent_locations) > 1: - logging.error('Multiple parents have been found for %s', unicode(locator)) - return modulestore().get_item(parent_locations[0]) + return modulestore().get_item(parent_location) def is_unit(xblock): diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py index f4e226cffc..db1cacf9d0 100644 --- a/cms/djangoapps/contentstore/views/import_export.py +++ b/cms/djangoapps/contentstore/views/import_export.py @@ -217,13 +217,13 @@ def import_handler(request, course_key_string): shutil.move(dirpath / fname, course_dir) _module_store, course_items = import_from_xml( - modulestore('direct'), + modulestore(), + request.user.id, settings.GITHUB_REPO_ROOT, [course_subdir], load_error_modules=False, static_content_store=contentstore(), target_course_id=course_key, - draft_store=modulestore() ) new_location = course_items[0].location @@ -322,7 +322,7 @@ def export_handler(request, course_key_string): root_dir = path(mkdtemp()) try: - export_to_xml(modulestore('direct'), contentstore(), course_module.id, root_dir, name, modulestore()) + export_to_xml(modulestore(), contentstore(), course_module.id, root_dir, name) logging.debug('tar file being generated at {0}'.format(export_file.name)) with tarfile.open(name=export_file.name, mode='w:gz') as tar_file: @@ -334,10 +334,10 @@ def export_handler(request, course_key_string): parent = None try: failed_item = modulestore().get_item(exc.location) - parent_locs = modulestore().get_parent_locations(failed_item.location) + parent_loc = modulestore().get_parent_location(failed_item.location) - if len(parent_locs) > 0: - parent = modulestore().get_item(parent_locs[0]) + if parent_loc is not None: + parent = modulestore().get_item(parent_loc) if parent.location.category == 'vertical': unit = parent except: # pylint: disable=bare-except diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 46ef237772..e55f12dfdb 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -20,20 +20,19 @@ from xblock.fields import Scope from xblock.fragment import Fragment import xmodule +from xmodule.tabs import StaticTab, CourseTabList +from xmodule.modulestore import PublishState, REVISION_OPTION_ALL from xmodule.modulestore.django import modulestore +from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError, DuplicateItemError from xmodule.modulestore.inheritance import own_metadata from xmodule.x_module import PREVIEW_VIEWS, STUDIO_VIEW from util.json_request import expect_json, JsonResponse -from util.string_utils import str_to_bool - -from ..utils import get_modulestore from .access import has_course_access -from .helpers import _xmodule_recurse, xblock_has_own_studio_page -from contentstore.utils import compute_publish_state, PublishState -from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES +from .helpers import xblock_has_own_studio_page +from contentstore.utils import compute_publish_state from contentstore.views.preview import get_preview_fragment from edxmako.shortcuts import render_to_string from models.settings.course_grading import CourseGradingModel @@ -72,8 +71,7 @@ def xblock_handler(request, usage_key_string): The restful handler for xblock requests. DELETE - json: delete this xblock instance from the course. Supports query parameters "recurse" to delete - all children and "all_versions" to delete from all (mongo) versions. + json: delete this xblock instance from the course. GET json: returns representation of the xblock (locator id, data, and metadata). if ?fields=graderType, it returns the graderType for the unit instead of the above. @@ -115,19 +113,17 @@ def xblock_handler(request, usage_key_string): # right now can't combine output of this w/ output of _get_module_info, but worthy goal return JsonResponse(CourseGradingModel.get_section_grader_type(usage_key)) # TODO: pass fields to _get_module_info and only return those - rsp = _get_module_info(usage_key) + rsp = _get_module_info(usage_key, request.user) return JsonResponse(rsp) else: return HttpResponse(status=406) elif request.method == 'DELETE': - delete_children = str_to_bool(request.REQUEST.get('recurse', 'False')) - delete_all_versions = str_to_bool(request.REQUEST.get('all_versions', 'False')) - - return _delete_item_at_location(usage_key, delete_children, delete_all_versions, request.user) + _delete_item(usage_key, request.user) + return JsonResponse() else: # Since we have a usage_key, we are updating an existing xblock. return _save_item( - request, + request.user, usage_key, data=request.json.get('data'), children=request.json.get('children'), @@ -177,7 +173,7 @@ def xblock_view_handler(request, usage_key_string, view_name): accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: - store = get_modulestore(usage_key) + store = modulestore() xblock = store.get_item(usage_key) is_read_only = _is_xblock_read_only(xblock) container_views = ['container_preview', 'reorderable_container_child_preview'] @@ -264,14 +260,14 @@ def _is_xblock_read_only(xblock): return component_publish_state == PublishState.public -def _save_item(request, usage_key, data=None, children=None, metadata=None, nullout=None, +def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout=None, grader_type=None, publish=None): """ Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata. nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert to default). """ - store = get_modulestore(usage_key) + store = modulestore() try: existing_item = store.get_item(usage_key) @@ -279,8 +275,7 @@ def _save_item(request, usage_key, data=None, children=None, metadata=None, null if usage_key.category in CREATE_IF_NOT_FOUND: # New module at this location, for pages that are not pre-created. # Used for course info handouts. - store.create_and_save_xmodule(usage_key) - existing_item = store.get_item(usage_key) + existing_item = store.create_and_save_xmodule(usage_key, user.id) else: raise except InvalidLocationError: @@ -292,19 +287,17 @@ def _save_item(request, usage_key, data=None, children=None, metadata=None, null if publish: if publish == 'make_private': - _xmodule_recurse( - existing_item, - lambda i: modulestore().unpublish(i.location), - ignore_exception=ItemNotFoundError - ) + try: + store.unpublish(existing_item.location, user.id), + except ItemNotFoundError: + pass elif publish == 'create_draft': - # This recursively clones the existing item location to a draft location (the draft is - # implicit, because modulestore is a Draft modulestore) - _xmodule_recurse( - existing_item, - lambda i: modulestore().convert_to_draft(i.location), - ignore_exception=DuplicateItemError - ) + try: + # This recursively clones the item subtree and marks the copies as draft + store.convert_to_draft(existing_item.location, user.id) + except DuplicateItemError: + pass + if data: # TODO Allow any scope.content fields not just "data" (exactly like the get below this) @@ -346,10 +339,20 @@ def _save_item(request, usage_key, data=None, children=None, metadata=None, null field.write_to(existing_item, value) if callable(getattr(existing_item, "editor_saved", None)): - existing_item.editor_saved(request.user, old_metadata, old_content) + existing_item.editor_saved(user, old_metadata, old_content) # commit to datastore - store.update_item(existing_item, request.user.id) + store.update_item(existing_item, user.id) + + # for static tabs, their containing course also records their display name + if usage_key.category == 'static_tab': + course = store.get_course(usage_key.course_key) + # find the course's reference to this tab and update the name. + static_tab = CourseTabList.get_tab_by_slug(course.tabs, usage_key.name) + # only update if changed + if static_tab and static_tab['name'] != existing_item.display_name: + static_tab['name'] = existing_item.display_name + store.update_item(course, user.id) result = { 'id': unicode(usage_key), @@ -358,23 +361,12 @@ def _save_item(request, usage_key, data=None, children=None, metadata=None, null } if grader_type is not None: - result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type, request.user)) + result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type, user)) # Make public after updating the xblock, in case the caller asked # for both an update and a publish. if publish and publish == 'make_public': - def _publish(block): - # This is super gross, but prevents us from publishing something that - # we shouldn't. Ideally, all modulestores would have a consistant - # interface for publishing. However, as of now, only the DraftMongoModulestore - # does, so we have to check for the attribute explicitly. - store = get_modulestore(block.location) - store.publish(block.location, request.user.id) - - _xmodule_recurse( - existing_item, - _publish - ) + modulestore().publish(existing_item.location, user.id) # Note that children aren't being returned until we have a use case. return JsonResponse(result) @@ -392,7 +384,8 @@ def _create_item(request): if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() - parent = get_modulestore(category).get_item(usage_key) + store = modulestore() + parent = store.get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the request @@ -410,17 +403,31 @@ def _create_item(request): if display_name is not None: metadata['display_name'] = display_name - get_modulestore(category).create_and_save_xmodule( + store.create_and_save_xmodule( dest_usage_key, + request.user.id, definition_data=data, metadata=metadata, - system=parent.runtime, + runtime=parent.runtime, ) + # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so + # if we add one then we need to also add it to the policy information (i.e. metadata) + # we should remove this once we can break this reference from the course to static tabs + if category == 'static_tab': + course = store.get_course(dest_usage_key.course_key) + course.tabs.append( + StaticTab( + name=display_name, + url_slug=dest_usage_key.name, + ) + ) + store.update_item(course, request.user.id) + # TODO replace w/ nicer accessor if not 'detached' in parent.runtime.load_block_type(category)._class_tags: parent.children.append(dest_usage_key) - get_modulestore(parent.location).update_item(parent, request.user.id) + store.update_item(parent, request.user.id) return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)}) @@ -429,7 +436,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N """ Duplicate an existing xblock as a child of the supplied parent_usage_key. """ - store = get_modulestore(duplicate_source_usage_key) + store = modulestore() source_item = store.get_item(duplicate_source_usage_key) # Change the blockID to be unique. dest_usage_key = duplicate_source_usage_key.replace(name=uuid4().hex) @@ -445,14 +452,14 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N else: duplicate_metadata['display_name'] = _("Duplicate of '{0}'").format(source_item.display_name) - get_modulestore(category).create_and_save_xmodule( + dest_module = store.create_and_save_xmodule( dest_usage_key, - definition_data=source_item.data if hasattr(source_item, 'data') else None, + user.id, + definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content), metadata=duplicate_metadata, - system=source_item.runtime, + runtime=source_item.runtime, ) - dest_module = get_modulestore(category).get_item(dest_usage_key) # Children are not automatically copied over (and not all xblocks have a 'children' attribute). # Because DAGs are not fully supported, we need to actually duplicate each child as well. if source_item.has_children: @@ -460,10 +467,10 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N for child in source_item.children: dupe = _duplicate_item(dest_usage_key, child, user=user) dest_module.children.append(dupe) - get_modulestore(dest_usage_key).update_item(dest_module, user.id if user else None) + store.update_item(dest_module, user.id if user else None) if not 'detached' in source_item.runtime.load_block_type(category)._class_tags: - parent = get_modulestore(parent_usage_key).get_item(parent_usage_key) + parent = store.get_item(parent_usage_key) # If source was already a child of the parent, add duplicate immediately afterward. # Otherwise, add child to end. if duplicate_source_usage_key in parent.children: @@ -471,36 +478,28 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N parent.children.insert(source_index + 1, dest_usage_key) else: parent.children.append(dest_usage_key) - get_modulestore(parent_usage_key).update_item(parent, user.id if user else None) + store.update_item(parent, user.id if user else None) return dest_usage_key -def _delete_item_at_location(item_usage_key, delete_children=False, delete_all_versions=False, user=None): +def _delete_item(usage_key, user): """ - Deletes the item at with the given Location. - - It is assumed that course permissions have already been checked. + Deletes an existing xblock with the given usage_key. + If the xblock is a Static Tab, removes it from course.tabs as well. """ - store = get_modulestore(item_usage_key) + store = modulestore() - item = store.get_item(item_usage_key) + # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so + # if we add one then we need to also add it to the policy information (i.e. metadata) + # we should remove this once we can break this reference from the course to static tabs + if usage_key.category == 'static_tab': + course = store.get_course(usage_key.course_key) + existing_tabs = course.tabs or [] + course.tabs = [tab for tab in existing_tabs if tab.get('url_slug') != usage_key.name] + store.update_item(course, user.id) - if delete_children: - _xmodule_recurse(item, lambda i: store.delete_item(i.location, delete_all_versions=delete_all_versions)) - else: - store.delete_item(item.location, delete_all_versions=delete_all_versions) - - # cdodge: we need to remove our parent's pointer to us so that it is no longer dangling - if delete_all_versions: - parent_locs = modulestore('direct').get_parent_locations(item_usage_key) - - for parent_loc in parent_locs: - parent = modulestore('direct').get_item(parent_loc) - parent.children.remove(item_usage_key) - modulestore('direct').update_item(parent, user.id if user else None) - - return JsonResponse() + store.delete_item(usage_key, user.id) # pylint: disable=W0613 @@ -522,29 +521,30 @@ def orphan_handler(request, course_key_string): raise PermissionDenied() if request.method == 'DELETE': if request.user.is_staff: - items = modulestore().get_orphans(course_usage_key) + store = modulestore() + items = store.get_orphans(course_usage_key) for itemloc in items: - # get_orphans returns the deprecated string format + # get_orphans returns the deprecated string format w/o revision usage_key = course_usage_key.make_usage_key_from_deprecated_string(itemloc) - modulestore().delete_item(usage_key, delete_all_versions=True) + # need to delete all versions + store.delete_item(usage_key, request.user.id, revision=REVISION_OPTION_ALL) return JsonResponse({'deleted': items}) else: raise PermissionDenied() -def _get_module_info(usage_key, rewrite_static_links=True): +def _get_module_info(usage_key, user, rewrite_static_links=True): """ metadata, data, id representation of a leaf module fetcher. :param usage_key: A UsageKey """ - store = get_modulestore(usage_key) + store = modulestore() try: module = store.get_item(usage_key) except ItemNotFoundError: if usage_key.category in CREATE_IF_NOT_FOUND: # Create a new one for certain categories only. Used for course info handouts. - store.create_and_save_xmodule(usage_key) - module = store.get_item(usage_key) + module = store.create_and_save_xmodule(usage_key, user.id) else: raise diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index e69a3d177f..6fc0dbe43b 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -117,7 +117,7 @@ def reorder_tabs_handler(course_item, request): # persist the new order of the tabs course_item.tabs = new_tab_list - modulestore('direct').update_item(course_item, request.user.id) + modulestore().update_item(course_item, request.user.id) return JsonResponse() @@ -140,7 +140,7 @@ def edit_tab_handler(course_item, request): if 'is_hidden' in request.json: # set the is_hidden attribute on the requested tab tab.is_hidden = request.json['is_hidden'] - modulestore('direct').update_item(course_item, request.user.id) + modulestore().update_item(course_item, request.user.id) else: raise NotImplementedError('Unsupported request to edit tab: {0}'.format(request.json)) @@ -163,7 +163,7 @@ def get_tab_by_locator(tab_list, usage_key_string): Look for a tab with the specified locator. Returns the first matching tab. """ tab_location = UsageKey.from_string(usage_key_string) - item = modulestore('direct').get_item(tab_location) + item = modulestore().get_item(tab_location) static_tab = StaticTab( name=item.display_name, url_slug=item.location.name, @@ -192,7 +192,7 @@ def primitive_delete(course, num): # Note for future implementations: if you delete a static_tab, then Chris Dodge # points out that there's other stuff to delete beyond this element. # This code happens to not delete static_tab so it doesn't come up. - modulestore('direct').update_item(course, '**replace_user**') + modulestore().update_item(course, '**replace_user**') def primitive_insert(course, num, tab_type, name): @@ -201,5 +201,5 @@ def primitive_insert(course, num, tab_type, name): new_tab = CourseTab.from_json({u'type': unicode(tab_type), u'name': unicode(name)}) tabs = course.tabs tabs.insert(num, new_tab) - modulestore('direct').update_item(course, '**replace_user**') + modulestore().update_item(course, '**replace_user**') diff --git a/cms/djangoapps/contentstore/views/tests/test_assets.py b/cms/djangoapps/contentstore/views/tests/test_assets.py index aeec07b378..5d81505f30 100644 --- a/cms/djangoapps/contentstore/views/tests/test_assets.py +++ b/cms/djangoapps/contentstore/views/tests/test_assets.py @@ -48,9 +48,10 @@ class BasicAssetsTestCase(AssetsTestCase): self.assertEquals(path, '/static/my_file_name.jpg') def test_pdf_asset(self): - module_store = modulestore('direct') + module_store = modulestore() _, course_items = import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['toy'], static_content_store=contentstore(), @@ -191,9 +192,10 @@ class LockAssetTestCase(AssetsTestCase): return json.loads(resp.content) # Load the toy course. - module_store = modulestore('direct') + module_store = modulestore() _, course_items = import_from_xml( module_store, + '**replace_user**', 'common/test/data/', ['toy'], static_content_store=contentstore(), diff --git a/cms/djangoapps/contentstore/views/tests/test_checklists.py b/cms/djangoapps/contentstore/views/tests/test_checklists.py index 2ae0c2c363..b8729151cf 100644 --- a/cms/djangoapps/contentstore/views/tests/test_checklists.py +++ b/cms/djangoapps/contentstore/views/tests/test_checklists.py @@ -1,7 +1,8 @@ """ Unit tests for checklist methods in views.py. """ -from contentstore.utils import get_modulestore, reverse_course_url +from contentstore.utils import reverse_course_url from contentstore.views.checklist import expand_checklist_action_url from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.django import modulestore import json from contentstore.tests.utils import CourseTestCase @@ -21,8 +22,7 @@ class ChecklistTestCase(CourseTestCase): def get_persisted_checklists(self): """ Returns the checklists as persisted in the modulestore. """ - modulestore = get_modulestore(self.course.location) - return modulestore.get_item(self.course.location).checklists + return modulestore().get_item(self.course.location).checklists def compare_checklists(self, persisted, request): """ @@ -54,8 +54,7 @@ class ChecklistTestCase(CourseTestCase): self.course.checklists = None # Save the changed `checklists` to the underlying KeyValueStore before updating the modulestore self.course.save() - modulestore = get_modulestore(self.course.location) - modulestore.update_item(self.course, self.user.id) + modulestore().update_item(self.course, self.user.id) self.assertEqual(self.get_persisted_checklists(), None) response = self.client.get(self.checklists_url) self.assertEqual(payload, response.content) diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py index ea4c0fda51..9c942dace3 100644 --- a/cms/djangoapps/contentstore/views/tests/test_container_page.py +++ b/cms/djangoapps/contentstore/views/tests/test_container_page.py @@ -3,8 +3,9 @@ Unit tests for the container page. """ import re -from contentstore.utils import compute_publish_state, PublishState +from contentstore.utils import compute_publish_state from contentstore.views.tests.utils import StudioPageTestCase +from xmodule.modulestore import PublishState from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.factories import ItemFactory @@ -29,6 +30,7 @@ class ContainerPageTestCase(StudioPageTestCase): category='vertical', display_name='Child Vertical') self.video = ItemFactory.create(parent_location=self.child_vertical.location, category="video", display_name="My Video") + self.store = modulestore() def test_container_html(self): self._test_html_content( @@ -49,12 +51,12 @@ class ContainerPageTestCase(StudioPageTestCase): Create the scenario of an xblock with children (non-vertical) on the container page. This should create a container page that is a child of another container page. """ - published_container = ItemFactory.create( + draft_container = ItemFactory.create( parent_location=self.child_container.location, category="wrapper", display_name="Wrapper" ) ItemFactory.create( - parent_location=published_container.location, + parent_location=draft_container.location, category="html", display_name="Child HTML" ) @@ -63,7 +65,7 @@ class ContainerPageTestCase(StudioPageTestCase): xblock, expected_section_tag=( '