diff --git a/cms/djangoapps/contentstore/management/commands/clone_course.py b/cms/djangoapps/contentstore/management/commands/clone_course.py index 8f87e04336..a18924418e 100644 --- a/cms/djangoapps/contentstore/management/commands/clone_course.py +++ b/cms/djangoapps/contentstore/management/commands/clone_course.py @@ -7,6 +7,7 @@ from student.roles import CourseInstructorRole, CourseStaffRole from opaque_keys.edx.keys import CourseKey from opaque_keys import InvalidKeyError from opaque_keys.edx.locations import SlashSeparatedCourseKey +from xmodule.modulestore import ModuleStoreEnum # @@ -38,7 +39,7 @@ class Command(BaseCommand): print("Cloning course {0} to {1}".format(source_course_id, dest_course_id)) with mstore.bulk_write_operations(dest_course_id): - if mstore.clone_course(source_course_id, dest_course_id, None): + if mstore.clone_course(source_course_id, dest_course_id, ModuleStoreEnum.UserID.mgmt_command): 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( diff --git a/cms/djangoapps/contentstore/management/commands/delete_course.py b/cms/djangoapps/contentstore/management/commands/delete_course.py index 89e88b9f9d..6f77c8a048 100644 --- a/cms/djangoapps/contentstore/management/commands/delete_course.py +++ b/cms/djangoapps/contentstore/management/commands/delete_course.py @@ -7,7 +7,7 @@ from contentstore.utils import delete_course_and_groups from opaque_keys.edx.keys import CourseKey from opaque_keys import InvalidKeyError from opaque_keys.edx.locations import SlashSeparatedCourseKey - +from xmodule.modulestore import ModuleStoreEnum class Command(BaseCommand): help = '''Delete a MongoDB backed course''' @@ -28,6 +28,6 @@ class Command(BaseCommand): if commit: print('Actually going to delete the course from DB....') - if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"): - if query_yes_no("Are you sure. This action cannot be undone!", default="no"): - delete_course_and_groups(course_key, commit) + if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"): + if query_yes_no("Are you sure. This action cannot be undone!", default="no"): + delete_course_and_groups(course_key, ModuleStoreEnum.UserID.mgmt_command) diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index e0e1ed0c5d..b0d550228b 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -6,6 +6,7 @@ from django.core.management.base import BaseCommand, CommandError, make_option from django_comment_common.utils import (seed_permissions_roles, are_permissions_roles_seeded) from xmodule.modulestore.xml_importer import import_from_xml +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.contentstore.django import contentstore @@ -40,7 +41,7 @@ class Command(BaseCommand): mstore = modulestore() _, course_items = import_from_xml( - mstore, "**replace_user**", data_dir, course_dirs, load_error_modules=False, + mstore, ModuleStoreEnum.UserID.mgmt_command, 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/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 260702b52a..5f396467ca 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -30,7 +30,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.inheritance import own_metadata from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation -from xmodule.modulestore.store_utilities import delete_course from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.xml_exporter import export_to_xml from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint @@ -632,7 +631,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase): self.store.convert_to_draft(vertical.location, self.user.id) # delete the course - delete_course(self.store, content_store, course_id, commit=True) + self.store.delete_course(course_id, self.user.id) # assert that there's absolutely no non-draft modules in the course # this should also include all draft items @@ -697,7 +696,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase): self.assertEqual(on_disk['course/2012_Fall'], own_metadata(course)) # remove old course - delete_course(self.store, content_store, course_id, commit=True) + self.store.delete_course(course_id, self.user.id) # reimport over old course self.check_import(root_dir, content_store, course_id) @@ -909,7 +908,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase): # Delete the course from module store and reimport it - delete_course(self.store, content_store, course_id, commit=True) + self.store.delete_course(course_id, self.user.id) import_from_xml( self.store, self.user.id, root_dir, ['test_export_no_content_store'], @@ -997,7 +996,7 @@ class ContentStoreTest(ContentStoreTestCase): test_course_data = self.assert_created_course(number_suffix=uuid4().hex) course_id = _get_course_id(test_course_data) self.assertTrue(are_permissions_roles_seeded(course_id)) - delete_course_and_groups(course_id, commit=True) + delete_course_and_groups(course_id, self.user.id) # should raise an exception for checking permissions on deleted course with self.assertRaises(ItemNotFoundError): are_permissions_roles_seeded(course_id) @@ -1009,7 +1008,7 @@ class ContentStoreTest(ContentStoreTestCase): # unseed the forums for the first course course_id = _get_course_id(test_course_data) - delete_course_and_groups(course_id, commit=True) + delete_course_and_groups(course_id, self.user.id) # should raise an exception for checking permissions on deleted course with self.assertRaises(ItemNotFoundError): are_permissions_roles_seeded(course_id) @@ -1029,7 +1028,7 @@ class ContentStoreTest(ContentStoreTestCase): self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id)) self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id)) # pylint: disable=no-member - delete_course_and_groups(course_id, commit=True) + delete_course_and_groups(course_id, self.user.id) # check that user's enrollment for this course is not deleted self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id)) # check that user has form role "Student" for this course even after deleting it @@ -1051,7 +1050,7 @@ class ContentStoreTest(ContentStoreTestCase): self.assertTrue(len(instructor_role.users_with_role()) > 0) # Now delete course and check that user not in instructor groups of this course - delete_course_and_groups(course_id, commit=True) + delete_course_and_groups(course_id, self.user.id) # Update our cached user since its roles have changed self.user = User.objects.get_by_natural_key(self.user.natural_key()[0]) diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py index 7d8c726db9..94642b6591 100644 --- a/cms/djangoapps/contentstore/tests/test_course_listing.py +++ b/cms/djangoapps/contentstore/tests/test_course_listing.py @@ -145,7 +145,7 @@ class TestCourseListing(ModuleStoreTestCase): self.assertEqual(courses_list, courses_list_by_groups) # now delete this course and re-add user to instructor group of this course - delete_course_and_groups(course_key, commit=True) + delete_course_and_groups(course_key, self.user.id) CourseInstructorRole(course_key).add_users(self.user) @@ -237,7 +237,7 @@ class TestCourseListing(ModuleStoreTestCase): self.assertEqual(len(courses_list_by_groups), 2) # now delete first course (course_location_caps) and check that it is no longer accessible - delete_course_and_groups(course_location_caps, commit=True) + delete_course_and_groups(course_location_caps, self.user.id) # test that get courses through iterating all courses now returns one course courses_list = _accessible_courses_list(self.request) @@ -269,7 +269,7 @@ class TestCourseListing(ModuleStoreTestCase): course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun') self._create_course_with_access_groups(course_location, self.user) - store.delete_course(course_location) + store.delete_course(course_location, self.user.id) course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun') course = self._create_course_with_access_groups(course_location, self.user) diff --git a/cms/djangoapps/contentstore/tests/test_crud.py b/cms/djangoapps/contentstore/tests/test_crud.py index 0accd31b8e..8910b31fb5 100644 --- a/cms/djangoapps/contentstore/tests/test_crud.py +++ b/cms/djangoapps/contentstore/tests/test_crud.py @@ -162,7 +162,7 @@ class TemplateTests(unittest.TestCase): self.assertIsInstance(self.split_store.get_course(id_locator), CourseDescriptor) # and by guid self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor) - self.split_store.delete_course(id_locator) + self.split_store.delete_course(id_locator, ModuleStoreEnum.UserID.test) # test can no longer retrieve by id self.assertRaises(ItemNotFoundError, self.split_store.get_course, id_locator) # but can by guid @@ -187,11 +187,11 @@ class TemplateTests(unittest.TestCase): ) first_problem.max_attempts = 3 first_problem.save() # decache the above into the kvs - updated_problem = self.split_store.update_item(first_problem, '**replace_user**') + updated_problem = self.split_store.update_item(first_problem, ModuleStoreEnum.UserID.test) 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 = self.split_store.delete_item(updated_problem.location, '**replace_user**', 'testbot') + updated_loc = self.split_store.delete_item(updated_problem.location, ModuleStoreEnum.UserID.test, 'testbot') second_problem = persistent_factories.ItemFactory.create( display_name='problem 2', diff --git a/cms/djangoapps/contentstore/tests/test_export_git.py b/cms/djangoapps/contentstore/tests/test_export_git.py index 4d38b29618..7928a9ed22 100644 --- a/cms/djangoapps/contentstore/tests/test_export_git.py +++ b/cms/djangoapps/contentstore/tests/test_export_git.py @@ -65,7 +65,7 @@ class TestExportGit(CourseTestCase): Test failed course export response. """ self.course_module.giturl = 'foobar' - modulestore().update_item(self.course_module, '**replace_user**') + modulestore().update_item(self.course_module, self.user.id) response = self.client.get('{}?action=push'.format(self.test_url)) self.assertIn('Export Failed:', response.content) @@ -75,7 +75,7 @@ class TestExportGit(CourseTestCase): Regression test for making sure errors are properly stringified """ self.course_module.giturl = 'foobar' - modulestore().update_item(self.course_module, '**replace_user**') + modulestore().update_item(self.course_module, self.user.id) response = self.client.get('{}?action=push'.format(self.test_url)) self.assertNotIn('django.utils.functional.__proxy__', response.content) @@ -98,7 +98,7 @@ class TestExportGit(CourseTestCase): self.populate_course() self.course_module.giturl = 'file://{}'.format(bare_repo_dir) - modulestore().update_item(self.course_module, '**replace_user**') + modulestore().update_item(self.course_module, self.user.id) response = self.client.get('{}?action=push'.format(self.test_url)) self.assertIn('Export Succeeded', response.content) diff --git a/cms/djangoapps/contentstore/tests/test_import.py b/cms/djangoapps/contentstore/tests/test_import.py index 3f8ed0b05d..96e121ca9a 100644 --- a/cms/djangoapps/contentstore/tests/test_import.py +++ b/cms/djangoapps/contentstore/tests/test_import.py @@ -33,25 +33,10 @@ class ContentStoreImportTest(ModuleStoreTestCase): NOTE: refactor using CourseFactory so they do not. """ def setUp(self): - - uname = 'testuser' - email = 'test+courses@edx.org' - password = 'foo' - - # Create the use so we can log them in. - self.user = User.objects.create_user(uname, email, password) - - # Note that we do not actually need to do anything - # for registration if we directly mark them active. - self.user.is_active = True - # Staff has access to view all courses - self.user.is_staff = True - - # Save the data that we've just changed to the db. - self.user.save() - + password = super(ContentStoreImportTest, self).setUp() + self.client = Client() - self.client.login(username=uname, password=password) + self.client.login(username=self.user.username, password=password) def tearDown(self): contentstore().drop_database() @@ -66,7 +51,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): module_store = modulestore() import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['test_import_course'], static_content_store=content_store, @@ -86,7 +71,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): module_store, __, course = self.load_test_import_course() __, course_items = import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data', ['test_import_course_2'], target_course_id=course.id, @@ -102,7 +87,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): course_id = SlashSeparatedCourseKey(u'Юникода', u'unicode_course', u'échantillon') import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['2014_Uni'], target_course_id=course_id @@ -148,7 +133,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): content_store = contentstore() 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) + import_from_xml(module_store, self.user.id, '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')) @@ -159,7 +144,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): def test_no_static_link_rewrites_on_import(self): module_store = modulestore() - _, courses = import_from_xml(module_store, '**replace_user**', 'common/test/data/', ['toy'], do_import_static=False, verbose=True) + _, courses = import_from_xml(module_store, self.user.id, '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')) @@ -178,7 +163,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['conditional'], target_course_id=target_course_id @@ -208,7 +193,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): target_course_id = SlashSeparatedCourseKey('testX', 'peergrading_copy', 'copy_run') import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['open_ended'], target_course_id=target_course_id @@ -227,7 +212,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): target_course_id = SlashSeparatedCourseKey('testX', 'split_test_copy', 'copy_run') import_from_xml( module_store, - '**replace_user**', + self.user.id, '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 090bf69921..dd2175b662 100644 --- a/cms/djangoapps/contentstore/tests/test_import_draft_order.py +++ b/cms/djangoapps/contentstore/tests/test_import_draft_order.py @@ -10,7 +10,7 @@ class DraftReorderTestCase(ModuleStoreTestCase): def test_order(self): store = modulestore() - _, course_items = import_from_xml(store, '**replace_user**', 'common/test/data/', ['import_draft_order']) + _, course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['import_draft_order']) course_key = course_items[0].id sequential = store.get_item(course_key.make_usage_key('sequential', '0f4f7649b10141b0bdc9922dcf94515a')) verticals = sequential.children diff --git a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py index d87419ca3b..56a6907908 100644 --- a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py +++ b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py @@ -28,9 +28,6 @@ class StubXBlock(XBlock): class XBlockImportTest(ModuleStoreTestCase): - def setUp(self): - self.store = modulestore() - @XBlock.register_temp_plugin(StubXBlock) def test_import_public(self): self._assert_import( @@ -62,7 +59,7 @@ class XBlockImportTest(ModuleStoreTestCase): """ _, courses = import_from_xml( - self.store, '**replace_user**', 'common/test/data', [course_dir] + self.store, self.user.id, 'common/test/data', [course_dir] ) xblock_location = courses[0].id.make_usage_key('stubxblock', 'xblock_test') diff --git a/cms/djangoapps/contentstore/tests/test_permissions.py b/cms/djangoapps/contentstore/tests/test_permissions.py index 8398ef57a8..0e08f1eb17 100644 --- a/cms/djangoapps/contentstore/tests/test_permissions.py +++ b/cms/djangoapps/contentstore/tests/test_permissions.py @@ -24,23 +24,10 @@ class TestCourseAccess(ModuleStoreTestCase): Create a pool of users w/o granting them any permissions """ - super(TestCourseAccess, self).setUp() - uname = 'testuser' - email = 'test+courses@edx.org' - password = 'foo' - - # Create the use so we can log them in. - self.user = User.objects.create_user(uname, email, password) - - # Note that we do not actually need to do anything - # for registration if we directly mark them active. - self.user.is_active = True - # Staff has access to view all courses - self.user.is_staff = True - self.user.save() + user_password = super(TestCourseAccess, self).setUp() self.client = AjaxEnabledTestClient() - self.client.login(username=uname, password=password) + self.client.login(username=self.user.username, password=user_password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun') diff --git a/cms/djangoapps/contentstore/tests/test_users_default_role.py b/cms/djangoapps/contentstore/tests/test_users_default_role.py index 26ec08ab94..c02b6574f1 100644 --- a/cms/djangoapps/contentstore/tests/test_users_default_role.py +++ b/cms/djangoapps/contentstore/tests/test_users_default_role.py @@ -62,7 +62,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase): # check that user has his default "Student" forum role for this course self.assertTrue(self.user.roles.filter(name="Student", course_id=self.course_key)) # pylint: disable=no-member - delete_course_and_groups(self.course_key, commit=True) + delete_course_and_groups(self.course_key, self.user.id) # check that user's enrollment for this course is not deleted self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key)) @@ -80,7 +80,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase): self.assertTrue(self.user.roles.filter(name="Student", course_id=self.course_key)) # pylint: disable=no-member # delete this course and recreate this course with same user - delete_course_and_groups(self.course_key, commit=True) + delete_course_and_groups(self.course_key, self.user.id) resp = self._create_course_with_given_location(self.course_key) self.assertEqual(resp.status_code, 200) @@ -98,7 +98,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase): # check that user has enrollment and his default "Student" forum role for this course self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key)) # delete this course and recreate this course with same user - delete_course_and_groups(self.course_key, commit=True) + delete_course_and_groups(self.course_key, self.user.id) # now create same course with different name case ('uppercase') new_course_key = self.course_key.replace(course=self.course_key.course.upper()) diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index f850c32b8e..10ed2f580e 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -9,6 +9,7 @@ from django.test import TestCase from django.test.utils import override_settings from contentstore import utils +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.factories import CourseFactory from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location @@ -193,7 +194,7 @@ class XBlockVisibilityTestCase(TestCase): """Tests for xblock visibility for students.""" def setUp(self): - self.dummy_user = 123 + self.dummy_user = ModuleStoreEnum.UserID.test self.past = datetime(1970, 1, 1) self.future = datetime.now(UTC) + timedelta(days=1) diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index 9fddf53bcb..e95bfd92a3 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -6,8 +6,8 @@ Utilities for contentstore tests import json import re -from django.contrib.auth.models import User from django.test.client import Client +from django.contrib.auth.models import User from xmodule.contentstore.django import contentstore from xmodule.contentstore.content import StaticContent @@ -68,53 +68,32 @@ class AjaxEnabledTestClient(Client): class CourseTestCase(ModuleStoreTestCase): def setUp(self): """ - These tests need a user in the DB so that the django Test Client - can log them in. + These tests need a user in the DB so that the django Test Client can log them in. + The test user is created in the ModuleStoreTestCase setUp method. They inherit from the ModuleStoreTestCase class so that the mongodb collection will be cleared out before each test case execution and deleted afterwards. """ - uname = 'testuser' - email = 'test+courses@edx.org' - password = 'foo' - - # Create the user so we can log them in. - self.user = User.objects.create_user(uname, email, password) - - # Note that we do not actually need to do anything - # for registration if we directly mark them active. - self.user.is_active = True - # Staff has access to view all courses - self.user.is_staff = True - self.user.save() + user_password = super(CourseTestCase, self).setUp() self.client = AjaxEnabledTestClient() - self.client.login(username=uname, password=password) + self.client.login(username=self.user.username, password=user_password) self.course = CourseFactory.create( org='MITx', number='999', display_name='Robot Super Course', ) - self.store = modulestore() def create_non_staff_authed_user_client(self, authenticate=True): """ Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing. """ - uname = 'teststudent' - password = 'foo' - nonstaff = User.objects.create_user(uname, 'test+student@edx.org', password) - - # Note that we do not actually need to do anything - # for registration if we directly mark them active. - nonstaff.is_active = True - nonstaff.is_staff = False - nonstaff.save() + nonstaff, password = self.create_non_staff_user() client = Client() if authenticate: - client.login(username=uname, password=password) + client.login(username=nonstaff.username, password=password) return client, nonstaff def populate_course(self): diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 7bd69b668d..c61753efa3 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -11,12 +11,10 @@ from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from xmodule.contentstore.content import StaticContent -from xmodule.contentstore.django import contentstore from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location -from xmodule.modulestore.store_utilities import delete_course from student.roles import CourseInstructorRole, CourseStaffRole @@ -28,27 +26,25 @@ NOTES_PANEL = {"name": _("My Notes"), "type": "notes"} EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]]) -def delete_course_and_groups(course_id, commit=False): +def delete_course_and_groups(course_id, user_id): """ 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() - content_store = contentstore() with module_store.bulk_write_operations(course_id): - if delete_course(module_store, content_store, course_id, commit): + module_store.delete_course(course_id, user_id) - 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)) + print 'removing User permissions from course....' + # in the django layer, we need to remove all the user permissions groups associated with this course + 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): diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 63eb9f5aa0..5c61efa9f9 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -449,8 +449,8 @@ def component_handler(request, usage_key_string, handler, suffix=''): log.info("XBlock %s attempted to access missing handler %r", descriptor, handler, exc_info=True) raise Http404 - # unintentional update to handle any side effects of handle call; so, request user didn't author - # the change - modulestore().update_item(descriptor, None) + # unintentional update to handle any side effects of handle call + # could potentially be updating actual course data or simply caching its values + modulestore().update_item(descriptor, request.user.id) return webob_to_django_response(resp) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 4ff410fecb..b144a6e733 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -337,6 +337,7 @@ def create_new_course(request): new_course = modulestore().create_course( course_key.org, course_key.offering, + request.user.id, fields=fields, ) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 4de9ad2dee..07d58a1613 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -140,8 +140,8 @@ def xblock_handler(request, usage_key_string): dest_usage_key = _duplicate_item( parent_usage_key, duplicate_source_usage_key, - request.json.get('display_name'), request.user, + request.json.get('display_name'), ) return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)}) @@ -193,8 +193,7 @@ def xblock_view_handler(request, usage_key_string, view_name): log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) - # change not authored by requestor but by xblocks. - store.update_item(xblock, None) + store.update_item(xblock, request.user.id) elif view_name in (unit_views + container_views): is_container_view = (view_name in container_views) @@ -432,7 +431,7 @@ def _create_item(request): return JsonResponse({"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)}) -def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=None, user=None): +def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_name=None): """ Duplicate an existing xblock as a child of the supplied parent_usage_key. """ @@ -454,6 +453,8 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N dest_module = store.create_and_save_xmodule( dest_usage_key, + + user.id, definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content), metadata=duplicate_metadata, @@ -467,7 +468,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N for child in source_item.children: dupe = _duplicate_item(dest_module.location, child, user=user) dest_module.children.append(dupe) - store.update_item(dest_module, user.id if user else None) + store.update_item(dest_module, user.id) if not 'detached' in source_item.runtime.load_block_type(category)._class_tags: parent = store.get_item(parent_usage_key) @@ -478,7 +479,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N parent.children.insert(source_index + 1, dest_module.location) else: parent.children.append(dest_module.location) - store.update_item(parent, user.id if user else None) + store.update_item(parent, user.id) return dest_module.location diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index 6fc0dbe43b..e251e3dca8 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -12,6 +12,7 @@ from django_future.csrf import ensure_csrf_cookie from django.views.decorators.http import require_http_methods from edxmako.shortcuts import render_to_response from xmodule.modulestore.django import modulestore +from xmodule.modulestore import ModuleStoreEnum from xmodule.tabs import CourseTabList, StaticTab, CourseTab, InvalidTabsException from opaque_keys.edx.keys import CourseKey, UsageKey @@ -192,7 +193,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().update_item(course, '**replace_user**') + modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command) def primitive_insert(course, num, tab_type, name): @@ -201,5 +202,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().update_item(course, '**replace_user**') + modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command) diff --git a/cms/djangoapps/contentstore/views/tests/test_assets.py b/cms/djangoapps/contentstore/views/tests/test_assets.py index 5d81505f30..40767941fe 100644 --- a/cms/djangoapps/contentstore/views/tests/test_assets.py +++ b/cms/djangoapps/contentstore/views/tests/test_assets.py @@ -51,7 +51,7 @@ class BasicAssetsTestCase(AssetsTestCase): module_store = modulestore() _, course_items = import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['toy'], static_content_store=contentstore(), @@ -195,7 +195,7 @@ class LockAssetTestCase(AssetsTestCase): module_store = modulestore() _, course_items = import_from_xml( module_store, - '**replace_user**', + self.user.id, 'common/test/data/', ['toy'], static_content_store=contentstore(), diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py index 9c942dace3..5d9132e4f4 100644 --- a/cms/djangoapps/contentstore/views/tests/test_container_page.py +++ b/cms/djangoapps/contentstore/views/tests/test_container_page.py @@ -20,16 +20,11 @@ class ContainerPageTestCase(StudioPageTestCase): def setUp(self): super(ContainerPageTestCase, self).setUp() - self.vertical = ItemFactory.create(parent_location=self.sequential.location, - category='vertical', display_name='Unit') - self.html = ItemFactory.create(parent_location=self.vertical.location, - category="html", display_name="HTML") - self.child_container = ItemFactory.create(parent_location=self.vertical.location, - category='split_test', display_name='Split Test') - self.child_vertical = ItemFactory.create(parent_location=self.child_container.location, - category='vertical', display_name='Child Vertical') - self.video = ItemFactory.create(parent_location=self.child_vertical.location, - category="video", display_name="My Video") + self.vertical = self._create_item(self.sequential.location, 'vertical', 'Unit') + self.html = self._create_item(self.vertical.location, "html", "HTML") + self.child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test') + self.child_vertical = self._create_item(self.child_container.location, 'vertical', 'Child Vertical') + self.video = self._create_item(self.child_vertical.location, "video", "My Video") self.store = modulestore() def test_container_html(self): @@ -51,14 +46,8 @@ 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. """ - draft_container = ItemFactory.create( - parent_location=self.child_container.location, - category="wrapper", display_name="Wrapper" - ) - ItemFactory.create( - parent_location=draft_container.location, - category="html", display_name="Child HTML" - ) + draft_container = self._create_item(self.child_container.location, "wrapper", "Wrapper") + self._create_item(draft_container.location, "html", "Child HTML") def test_container_html(xblock): self._test_html_content( @@ -135,9 +124,8 @@ class ContainerPageTestCase(StudioPageTestCase): """ Verify that a public container rendered as a child of the container page returns the expected HTML. """ - empty_child_container = ItemFactory.create(parent_location=self.vertical.location, - category='split_test', display_name='Split Test') - published_empty_child_container = self.store.publish(empty_child_container.location, '**replace_user**') + empty_child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test') + published_empty_child_container = self.store.publish(empty_child_container.location, self.user.id) self.validate_preview_html(published_empty_child_container, self.reorderable_child_view, can_reorder=False, can_edit=False, can_add=False) @@ -145,7 +133,18 @@ class ContainerPageTestCase(StudioPageTestCase): """ Verify that a draft container rendered as a child of the container page returns the expected HTML. """ - empty_child_container = ItemFactory.create(parent_location=self.vertical.location, - category='split_test', display_name='Split Test') + empty_child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test') self.validate_preview_html(empty_child_container, self.reorderable_child_view, can_reorder=True, can_edit=True, can_add=False) + + def _create_item(self, parent_location, category, display_name, **kwargs): + """ + creates an item in the module store, without publishing it. + """ + return ItemFactory.create( + parent_location=parent_location, + category=category, + display_name=display_name, + publish_item=False, + **kwargs + ) diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index 2d4acc5e46..5666eeaa85 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -25,10 +25,9 @@ from contentstore.tests.utils import CourseTestCase from student.tests.factories import UserFactory from xmodule.capa_module import CapaDescriptor from xmodule.modulestore import PublishState -from xmodule.modulestore.django import modulestore from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW from xblock.exceptions import NoSuchHandlerError -from opaque_keys.edx.keys import UsageKey +from opaque_keys.edx.keys import UsageKey, CourseKey from opaque_keys.edx.locations import Location from xmodule.partitions.partitions import Group, UserPartition @@ -40,7 +39,6 @@ class ItemTest(CourseTestCase): self.course_key = self.course.id self.usage_key = self.course.location - self.store = modulestore() def get_item_from_modulestore(self, usage_key, verify_is_draft=False): """ diff --git a/cms/djangoapps/contentstore/views/tests/test_unit_page.py b/cms/djangoapps/contentstore/views/tests/test_unit_page.py index 7882ad858e..967d6628aa 100644 --- a/cms/djangoapps/contentstore/views/tests/test_unit_page.py +++ b/cms/djangoapps/contentstore/views/tests/test_unit_page.py @@ -40,7 +40,7 @@ class UnitPageTestCase(StudioPageTestCase): """ Verify that a public xblock's preview returns the expected HTML. """ - published_video = self.store.publish(self.video.location, '**replace_user**') + published_video = self.store.publish(self.video.location, self.user.id) self.validate_preview_html(self.video, STUDENT_VIEW, can_edit=True, can_reorder=True, can_add=False) @@ -60,7 +60,7 @@ class UnitPageTestCase(StudioPageTestCase): category='split_test', display_name='Split Test') ItemFactory.create(parent_location=child_container.location, category='html', display_name='grandchild') - published_child_container = self.store.publish(child_container.location, '**replace_user**') + published_child_container = self.store.publish(child_container.location, self.user.id) self.validate_preview_html(published_child_container, STUDENT_VIEW, can_reorder=True, can_edit=True, can_add=False) diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 615895329f..0db837f68b 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -53,7 +53,7 @@ class CourseMetadata(object): return result @classmethod - def update_from_json(cls, descriptor, jsondict, filter_tabs=True, user=None): + def update_from_json(cls, descriptor, jsondict, user, filter_tabs=True): """ Decode the json into CourseMetadata and save any changed attrs to the db. @@ -84,6 +84,6 @@ class CourseMetadata(object): setattr(descriptor, key, value) if len(key_values) > 0: - modulestore().update_item(descriptor, user.id if user else None) + modulestore().update_item(descriptor, user.id) return cls.fetch(descriptor) diff --git a/common/djangoapps/contentserver/tests/test.py b/common/djangoapps/contentserver/tests/test.py index 38b80774a6..5e1f3bf084 100644 --- a/common/djangoapps/contentserver/tests/test.py +++ b/common/djangoapps/contentserver/tests/test.py @@ -5,7 +5,6 @@ import copy import logging from uuid import uuid4 -from django.contrib.auth.models import User from django.conf import settings from django.test.client import Client from django.test.utils import override_settings @@ -34,12 +33,16 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """ Create user and login. """ + self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp() + self.staff_usr = self.user + self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user() + self.client = Client() self.contentstore = contentstore() self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') - import_from_xml(modulestore(), '**replace_user**', 'common/test/data/', ['toy'], + import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['toy'], static_content_store=self.contentstore, verbose=True) # A locked asset @@ -52,24 +55,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.contentstore.set_attr(self.locked_asset, 'locked', True) - # Create user - self.usr = 'testuser' - self.pwd = 'foo' - email = 'test+courses@edx.org' - self.user = User.objects.create_user(self.usr, email, self.pwd) - self.user.is_active = True - self.user.save() - - # Create staff user - self.staff_usr = 'stafftestuser' - self.staff_pwd = 'foo' - staff_email = 'stafftest+courses@edx.org' - self.staff_user = User.objects.create_user(self.staff_usr, staff_email, - self.staff_pwd) - self.staff_user.is_active = True - self.staff_user.is_staff = True - self.staff_user.save() - def tearDown(self): contentstore().drop_database() @@ -97,7 +82,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): Test that locked assets behave appropriately in case user is logged in in but not registered for the course. """ - self.client.login(username=self.usr, password=self.pwd) + self.client.login(username=self.non_staff_usr, password=self.non_staff_pwd) resp = self.client.get(self.url_locked) self.assertEqual(resp.status_code, 403) # pylint: disable=E1103 @@ -106,10 +91,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): Test that locked assets behave appropriately in case user is logged in and registered for the course. """ - CourseEnrollment.enroll(self.user, self.course_key) - self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key)) + CourseEnrollment.enroll(self.non_staff_usr, self.course_key) + self.assertTrue(CourseEnrollment.is_enrolled(self.non_staff_usr, self.course_key)) - self.client.login(username=self.usr, password=self.pwd) + self.client.login(username=self.non_staff_usr, password=self.non_staff_pwd) resp = self.client.get(self.url_locked) self.assertEqual(resp.status_code, 200) # pylint: disable=E1103 diff --git a/common/djangoapps/external_auth/tests/test_shib.py b/common/djangoapps/external_auth/tests/test_shib.py index fc312f0c63..bd050384aa 100644 --- a/common/djangoapps/external_auth/tests/test_shib.py +++ b/common/djangoapps/external_auth/tests/test_shib.py @@ -17,8 +17,8 @@ from django.utils.importlib import import_module from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config -from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.django import modulestore +from xmodule.modulestore import ModuleStoreEnum from opaque_keys.edx.locations import SlashSeparatedCourseKey from external_auth.models import ExternalAuthMap @@ -80,7 +80,8 @@ class ShibSPTest(ModuleStoreTestCase): request_factory = RequestFactory() def setUp(self): - self.store = modulestore() + super(ShibSPTest, self).setUp(create_user=False) + self.test_user_id = ModuleStoreEnum.UserID.test @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_exception_shib_login(self): @@ -377,13 +378,21 @@ class ShibSPTest(ModuleStoreTestCase): """ Tests that the correct course specific login and registration urls work for shib """ - course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') + course = CourseFactory.create( + org='MITx', + number='999', + display_name='Robot Super Course', + user_id=self.test_user_id, + ) # Test for cases where course is found for domain in ["", "shib:https://idp.stanford.edu/"]: # set domains - course.enrollment_domain = domain - self.store.update_item(course, '**replace_user**') + + # temporarily set the branch to draft-preferred so we can update the course + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course.id): + course.enrollment_domain = domain + self.store.update_item(course, self.test_user_id) # setting location to test that GET params get passed through login_request = self.request_factory.get('/course_specific_login/MITx/999/Robot_Super_Course' + @@ -450,13 +459,21 @@ class ShibSPTest(ModuleStoreTestCase): """ # create 2 course, one with limited enrollment one without - shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') - shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/' - self.store.update_item(shib_course, '**replace_user**') + shib_course = CourseFactory.create( + org='Stanford', + number='123', + display_name='Shib Only', + enrollment_domain='shib:https://idp.stanford.edu/', + user_id=self.test_user_id, + ) - open_enroll_course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') - open_enroll_course.enrollment_domain = '' - self.store.update_item(open_enroll_course, '**replace_user**') + open_enroll_course = CourseFactory.create( + org='MITx', + number='999', + display_name='Robot Super Course', + enrollment_domain='', + user_id=self.test_user_id, + ) # create 3 kinds of students, external_auth matching shib_course, external_auth not matching, no external auth shib_student = UserFactory.create() @@ -520,9 +537,13 @@ class ShibSPTest(ModuleStoreTestCase): student.save() extauth.save() - course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') - course.enrollment_domain = 'shib:https://idp.stanford.edu/' - self.store.update_item(course, '**replace_user**') + course = CourseFactory.create( + org='Stanford', + number='123', + display_name='Shib Only', + enrollment_domain='shib:https://idp.stanford.edu/', + user_id=self.test_user_id, + ) # use django test client for sessions and url processing # no enrollment before trying diff --git a/common/djangoapps/student/tests/test_course_listing.py b/common/djangoapps/student/tests/test_course_listing.py index 4625660913..970a6110d4 100644 --- a/common/djangoapps/student/tests/test_course_listing.py +++ b/common/djangoapps/student/tests/test_course_listing.py @@ -97,7 +97,7 @@ class TestCourseListing(ModuleStoreTestCase): course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun') self._create_course_with_access_groups(course_location) - mongo_store.delete_course(course_location) + mongo_store.delete_course(course_location, ModuleStoreEnum.UserID.test) course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun') course = self._create_course_with_access_groups(course_location) diff --git a/common/djangoapps/student/tests/test_login.py b/common/djangoapps/student/tests/test_login.py index 52a2b93e99..ad715a8dff 100644 --- a/common/djangoapps/student/tests/test_login.py +++ b/common/djangoapps/student/tests/test_login.py @@ -17,7 +17,6 @@ from student.views import _parse_course_id_from_string, _get_course_enrollment_d from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config -from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.django import modulestore from external_auth.models import ExternalAuthMap @@ -346,11 +345,20 @@ class ExternalAuthShibTest(ModuleStoreTestCase): Tests how login_user() interacts with ExternalAuth, in particular Shib """ def setUp(self): - self.store = modulestore() - self.course = CourseFactory.create(org='Stanford', number='456', display_name='NO SHIB') - self.shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') - self.shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/' - self.store.update_item(self.shib_course, '**replace_user**') + super(ExternalAuthShibTest, self).setUp() + self.course = CourseFactory.create( + org='Stanford', + number='456', + display_name='NO SHIB', + user_id=self.user.id, + ) + self.shib_course = CourseFactory.create( + org='Stanford', + number='123', + display_name='Shib Only', + enrollment_domain='shib:https://idp.stanford.edu/', + user_id=self.user.id, + ) self.user_w_map = UserFactory.create(email='withmap@stanford.edu') self.extauth = ExternalAuthMap(external_id='withmap@stanford.edu', external_email='withmap@stanford.edu', diff --git a/common/djangoapps/util/testing.py b/common/djangoapps/util/testing.py index 062b04c8a0..fdd61a2a6a 100644 --- a/common/djangoapps/util/testing.py +++ b/common/djangoapps/util/testing.py @@ -30,8 +30,8 @@ class UrlResetMixin(object): # Resolve a URL so that the new urlconf gets loaded resolve('/') - def setUp(self): + def setUp(self, **kwargs): """Reset django default urlconf before tests and after tests""" - super(UrlResetMixin, self).setUp() + super(UrlResetMixin, self).setUp(**kwargs) self._reset_urls() self.addCleanup(self._reset_urls) diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 8470e598e1..1d16cafa7a 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -72,6 +72,21 @@ class ModuleStoreEnum(object): draft = 'draft-branch' published = 'published-branch' + class UserID(object): + """ + Values for user ID defaults + """ + # Note: we use negative values here to (try to) not collide + # with user identifiers provided by actual user services. + + # user ID to use for all management commands + mgmt_command = -1 + + # user ID to use for primitive commands + primitive_command = -2 + + # user ID to use for tests that do not have a django user available + test = -3 class PublishState(object): """ @@ -270,6 +285,18 @@ class ModuleStoreRead(object): """ pass + @abstractmethod + def compute_publish_state(self, xblock): + """ + Returns whether this xblock is draft, public, or private. + + 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 + """ + pass class ModuleStoreWrite(ModuleStoreRead): """ @@ -280,7 +307,7 @@ class ModuleStoreWrite(ModuleStoreRead): __metaclass__ = ABCMeta @abstractmethod - def update_item(self, xblock, user_id=None, allow_not_found=False, force=False): + def update_item(self, xblock, user_id, allow_not_found=False, force=False): """ Update the given xblock's persisted repr. Pass the user's unique id which the persistent store should save with the update if it has that ability. @@ -296,7 +323,7 @@ class ModuleStoreWrite(ModuleStoreRead): pass @abstractmethod - def delete_item(self, location, user_id=None, **kwargs): + def delete_item(self, location, user_id, **kwargs): """ Delete an item and its subtree from persistence. Remove the item from any parents (Note, does not affect parents from other branches or logical branches; thus, in old mongo, deleting something @@ -315,7 +342,7 @@ class ModuleStoreWrite(ModuleStoreRead): pass @abstractmethod - def create_course(self, org, offering, user_id=None, fields=None, **kwargs): + def create_course(self, org, offering, user_id, fields=None, **kwargs): """ Creates and returns the course. @@ -348,7 +375,7 @@ class ModuleStoreWrite(ModuleStoreRead): pass @abstractmethod - def delete_course(self, course_key, user_id=None): + def delete_course(self, course_key, user_id): """ Deletes the course. It may be a soft or hard delete. It may or may not remove the xblock definitions depending on the persistence layer and how tightly bound the xblocks are to the course. @@ -441,6 +468,12 @@ class ModuleStoreReadBase(ModuleStoreRead): None ) + def compute_publish_state(self, xblock): + """ + Returns PublishState.public since this is a read-only store. + """ + return PublishState.public + def heartbeat(self): """ Is this modulestore ready? @@ -499,7 +532,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite): result[field.scope][field_name] = value return result - def update_item(self, xblock, user_id=None, allow_not_found=False, force=False): + def update_item(self, xblock, user_id, allow_not_found=False, force=False): """ Update the given xblock's persisted repr. Pass the user's unique id which the persistent store should save with the update if it has that ability. @@ -514,7 +547,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite): """ raise NotImplementedError - def delete_item(self, location, user_id=None, force=False): + def delete_item(self, location, user_id, force=False): """ Delete an item from persistence. Pass the user's unique id which the persistent store should save with the update if it has that ability. @@ -553,6 +586,15 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite): super(ModuleStoreWriteBase, self).clone_course(source_course_id, dest_course_id, user_id) return dest_course_id + def delete_course(self, course_key, user_id): + """ + This base method just deletes the assets. The lower level impls must do the actual deleting of + content. + """ + # delete the assets + self.contentstore.delete_all_course_assets(course_key) + super(ModuleStoreWriteBase, self).delete_course(course_key, user_id) + @contextmanager def bulk_write_operations(self, course_id): """ diff --git a/common/lib/xmodule/xmodule/modulestore/mixed.py b/common/lib/xmodule/xmodule/modulestore/mixed.py index 5fa139b44b..708838d6db 100644 --- a/common/lib/xmodule/xmodule/modulestore/mixed.py +++ b/common/lib/xmodule/xmodule/modulestore/mixed.py @@ -229,16 +229,13 @@ class MixedModuleStore(ModuleStoreWriteBase): store = self._get_modulestore_for_courseid(course_id) return store.has_course(course_id, ignore_case) - def delete_course(self, course_key, user_id=None): + def delete_course(self, course_key, user_id): """ See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course """ assert(isinstance(course_key, CourseKey)) store = self._get_modulestore_for_courseid(course_key) - if hasattr(store, 'delete_course'): - return store.delete_course(course_key, user_id) - else: - raise NotImplementedError(u"Cannot delete a course on store {}".format(store)) + return store.delete_course(course_key, user_id) def get_parent_location(self, location, **kwargs): """ @@ -276,7 +273,7 @@ class MixedModuleStore(ModuleStoreWriteBase): errs.update(store.get_errored_courses()) return errs - def create_course(self, org, offering, user_id=None, fields=None, **kwargs): + def create_course(self, org, offering, user_id, fields=None, **kwargs): """ Creates and returns the course. @@ -290,10 +287,6 @@ class MixedModuleStore(ModuleStoreWriteBase): Returns: a CourseDescriptor """ store = self._get_modulestore_for_courseid(None) - - if not hasattr(store, 'create_course'): - raise NotImplementedError(u"Cannot create a course on store {}".format(store)) - return store.create_course(org, offering, user_id, fields, **kwargs) def clone_course(self, source_course_id, dest_course_id, user_id): @@ -326,7 +319,7 @@ class MixedModuleStore(ModuleStoreWriteBase): source_course_id, user_id, dest_course_id.org, dest_course_id.offering ) - def create_item(self, course_or_parent_loc, category, user_id=None, **kwargs): + def create_item(self, course_or_parent_loc, category, user_id, **kwargs): """ Create and return the item. If parent_loc is a specific location v a course id, it installs the new item as a child of the parent (if the parent_loc is a specific @@ -353,7 +346,7 @@ class MixedModuleStore(ModuleStoreWriteBase): if parent_loc is not None and not 'detached' in xblock._class_tags: parent = store.get_item(parent_loc) parent.children.append(location) - store.update_item(parent) + store.update_item(parent, user_id) elif isinstance(store, SplitMongoModuleStore): if not isinstance(course_or_parent_loc, (CourseLocator, BlockUsageLocator)): raise ValueError(u"Cannot create a child of {} in split. Wrong repr.".format(course_or_parent_loc)) @@ -378,7 +371,7 @@ class MixedModuleStore(ModuleStoreWriteBase): store = self._verify_modulestore_support(xblock.location, 'update_item') return store.update_item(xblock, user_id, allow_not_found) - def delete_item(self, location, user_id=None, **kwargs): + def delete_item(self, location, user_id, **kwargs): """ Delete the given item from persistence. kwargs allow modulestore specific parameters. """ @@ -456,13 +449,7 @@ class MixedModuleStore(ModuleStoreWriteBase): """ course_id = xblock.scope_ids.usage_id.course_key store = self._get_modulestore_for_courseid(course_id) - if hasattr(store, 'compute_publish_state'): - return store.compute_publish_state(xblock) - elif hasattr(store, 'publish'): - raise NotImplementedError(u"Cannot compute_publish_state on store {}".format(store)) - else: - # read-only store; so, everything's public - return PublishState.public + return store.compute_publish_state(xblock) def publish(self, location, user_id): """ diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/base.py b/common/lib/xmodule/xmodule/modulestore/mongo/base.py index 2f71e83635..554c2a8a2b 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/base.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/base.py @@ -841,7 +841,7 @@ class MongoModuleStore(ModuleStoreWriteBase): modules = self._load_items(course_id, list(items)) return modules - def create_course(self, org, offering, user_id=None, fields=None, **kwargs): + def create_course(self, org, offering, user_id, fields=None, **kwargs): """ Creates and returns the course. @@ -892,15 +892,6 @@ class MongoModuleStore(ModuleStoreWriteBase): return course - def delete_course(self, course_key, user_id=None): - """ - The impl removes all of the db records for the course. - :param course_key: - :param user_id: - """ - course_query = self._course_key_to_son(course_key) - self.collection.remove(course_query, multi=True) - def create_xmodule(self, location, definition_data=None, metadata=None, runtime=None, fields={}): """ Create the new xmodule but don't save it. Returns the new module. @@ -992,7 +983,7 @@ class MongoModuleStore(ModuleStoreWriteBase): self._update_single_item(parent, update) self._update_ancestors(parent, update) - def update_item(self, xblock, user_id=None, allow_not_found=False, force=False, isPublish=False, + def update_item(self, xblock, user_id, allow_not_found=False, force=False, isPublish=False, is_publish_root=True): """ Update the persisted version of xblock to reflect its current values. diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py index ca13204f01..d4a5b197f7 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py @@ -144,6 +144,18 @@ class DraftModuleStore(MongoModuleStore): del key['_id.revision'] return self.collection.find(key).count() > 0 + def delete_course(self, course_key, user_id): + """ + :param course_key: which course to delete + :param user_id: id of the user deleting the course + """ + # delete the assets + super(DraftModuleStore, self).delete_course(course_key, user_id) + + # delete all of the db records for the course + course_query = self._course_key_to_son(course_key) + self.collection.remove(course_query, multi=True) + def clone_course(self, source_course_id, dest_course_id, user_id): """ Only called if cloning within this store or if env doesn't set up mixed. @@ -419,7 +431,7 @@ class DraftModuleStore(MongoModuleStore): # get_item will wrap_draft so don't call it here (otherwise, it would override the is_draft attribute) return self.get_item(location) - def update_item(self, xblock, user_id=None, allow_not_found=False, force=False, isPublish=False): + def update_item(self, xblock, user_id, allow_not_found=False, force=False, isPublish=False): """ See superclass doc. In addition to the superclass's behavior, this method converts the unit to draft if it's not diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py index c70a524d3c..5cbf3633ac 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py @@ -1380,7 +1380,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): return result - def delete_course(self, course_key, user_id=None): + def delete_course(self, course_key, user_id): """ Remove the given course from the course index. @@ -1395,6 +1395,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): log.info(u"deleting course from split-mongo: %s", course_key) self.db_connection.delete_course_index(index) + # We do NOT call the super class here since we need to keep the assets + # in case the course is later restored. + # super(SplitMongoModuleStore, self).delete_course(course_key, user_id) + def revert_to_published(self, location, user_id=None): """ Reverts an item to its last published version (recursively traversing all of its descendants). @@ -1407,13 +1411,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): """ raise NotImplementedError() - def get_errored_courses(self): - """ - This function doesn't make sense for the mongo modulestore, as structures - are loaded on demand, rather than up front - """ - return {} - def inherit_settings(self, block_map, block_json, inheriting_settings=None): """ Updates block_json with any inheritable setting set by an ancestor and recurses to children. diff --git a/common/lib/xmodule/xmodule/modulestore/store_utilities.py b/common/lib/xmodule/xmodule/modulestore/store_utilities.py index d1e40ade9c..cf69b605b8 100644 --- a/common/lib/xmodule/xmodule/modulestore/store_utilities.py +++ b/common/lib/xmodule/xmodule/modulestore/store_utilities.py @@ -85,25 +85,3 @@ def rewrite_nonportable_content_links(source_course_id, dest_course_id, text): logging.warning("Error producing regex substitution %r for text = %r.\n\nError msg = %s", source_course_id, text, str(exc)) return text - - -def delete_course(modulestore, contentstore, course_key, commit=False): - """ - This method will actually do the work to delete all content in a course in a MongoDB backed - courseware store. BE VERY CAREFUL, this is not reversable. - """ - - # check to see if the source course is actually there - if not modulestore.has_course(course_key): - raise Exception("Cannot find a course at {0}. Aborting".format(course_key)) - - if commit: - print "Deleting assets and thumbnails {}".format(course_key) - contentstore.delete_all_course_assets(course_key) - - # finally delete the course - print "Deleting {0}...".format(course_key) - if commit: - modulestore.delete_course(course_key, '**replace-user**') - - return True diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 8792f3b439..745784c85d 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -4,8 +4,10 @@ Modulestore configuration for test cases. from uuid import uuid4 from django.test import TestCase +from django.contrib.auth.models import User from xmodule.modulestore.django import ( - modulestore, clear_existing_modulestores, loc_mapper) + modulestore, clear_existing_modulestores, loc_mapper +) from xmodule.modulestore import ModuleStoreEnum @@ -126,18 +128,62 @@ class ModuleStoreTestCase(TestCase): `clear_existing_modulestores()` directly in your `setUp()` method. """ + def setUp(self, **kwargs): + """ + Creates a test User if `create_user` is True. + Returns the password for the test User. - @staticmethod - def update_course(course): + Args: + create_user - specifies whether or not to create a test User. Default is True. + """ + super(ModuleStoreTestCase, self).setUp() + + self.store = modulestore() + + uname = 'testuser' + email = 'test+courses@edx.org' + password = 'foo' + + if kwargs.pop('create_user', True): + # Create the user so we can log them in. + self.user = User.objects.create_user(uname, email, password) + + # Note that we do not actually need to do anything + # for registration if we directly mark them active. + self.user.is_active = True + + # Staff has access to view all courses + self.user.is_staff = True + self.user.save() + + return password + + def create_non_staff_user(self): + """ + Creates a non-staff test user. + Returns the non-staff test user and its password. + """ + uname = 'teststudent' + password = 'foo' + nonstaff_user = User.objects.create_user(uname, 'test+student@edx.org', password) + + # Note that we do not actually need to do anything + # for registration if we directly mark them active. + nonstaff_user.is_active = True + nonstaff_user.is_staff = False + nonstaff_user.save() + return nonstaff_user, password + + def update_course(self, course, user_id): """ Updates the version of course in the modulestore 'course' is an instance of CourseDescriptor for which we want to update metadata. """ - store = modulestore() - store.update_item(course, '**replace_user**') - updated_course = store.get_course(course.id) + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course.id): + self.store.update_item(course, user_id) + updated_course = self.store.get_course(course.id) return updated_course @staticmethod diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 17a574e8df..08ea97d744 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -2,7 +2,7 @@ from factory import Factory, lazy_attribute_sequence, lazy_attribute from factory.containers import CyclicDefinitionError from uuid import uuid4 -from xmodule.modulestore import prefer_xmodules +from xmodule.modulestore import prefer_xmodules, ModuleStoreEnum from opaque_keys.edx.locations import Location from xblock.core import XBlock from xmodule.tabs import StaticTab @@ -52,21 +52,23 @@ class CourseFactory(XModuleFactory): store = kwargs.pop('modulestore') name = kwargs.get('name', kwargs.get('run', Location.clean(kwargs.get('display_name')))) run = kwargs.get('run', name) + user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) location = Location(org, number, run, 'course', name) - # Write the data to the mongo datastore - new_course = store.create_xmodule(location, metadata=kwargs.get('metadata', None)) + with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + # Write the data to the mongo datastore + new_course = store.create_xmodule(location, metadata=kwargs.get('metadata', None)) - # The rest of kwargs become attributes on the course: - for k, v in kwargs.iteritems(): - setattr(new_course, k, v) + # The rest of kwargs become attributes on the course: + for k, v in kwargs.iteritems(): + setattr(new_course, k, v) - # Save the attributes we just set - new_course.save() - # Update the data in the mongo datastore - store.update_item(new_course, '**replace_user**') - return new_course + # Save the attributes we just set + new_course.save() + # Update the data in the mongo datastore + store.update_item(new_course, user_id) + return new_course class ItemFactory(XModuleFactory): @@ -128,6 +130,8 @@ class ItemFactory(XModuleFactory): :boilerplate: (optional) the boilerplate for overriding field values + :publish_item: (optional) whether or not to publish the item (default is True) + :target_class: is ignored """ @@ -143,7 +147,8 @@ class ItemFactory(XModuleFactory): display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') - user_id = kwargs.pop('user_id', 999) + user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) + publish_item = kwargs.pop('publish_item', True) assert isinstance(location, Location) assert location != parent_location @@ -153,47 +158,55 @@ class ItemFactory(XModuleFactory): # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) - if 'boilerplate' in kwargs: - template_id = kwargs.pop('boilerplate') - clz = XBlock.load_class(category, select=prefer_xmodules) - template = clz.get_template(template_id) - assert template is not None - metadata.update(template.get('metadata', {})) - if not isinstance(data, basestring): - data.update(template.get('data')) + with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): - # replace the display name with an optional parameter passed in from the caller - if display_name is not None: - metadata['display_name'] = display_name - runtime = parent.runtime if parent else None - store.create_and_save_xmodule(location, user_id, metadata=metadata, definition_data=data, runtime=runtime) + if 'boilerplate' in kwargs: + template_id = kwargs.pop('boilerplate') + clz = XBlock.load_class(category, select=prefer_xmodules) + template = clz.get_template(template_id) + assert template is not None + metadata.update(template.get('metadata', {})) + if not isinstance(data, basestring): + data.update(template.get('data')) - module = store.get_item(location) + # replace the display name with an optional parameter passed in from the caller + if display_name is not None: + metadata['display_name'] = display_name + runtime = parent.runtime if parent else None + store.create_and_save_xmodule(location, user_id, metadata=metadata, definition_data=data, runtime=runtime) - for attr, val in kwargs.items(): - setattr(module, attr, val) - # Save the attributes we just set - module.save() + module = store.get_item(location) - store.update_item(module, '**replace_user**') + for attr, val in kwargs.items(): + setattr(module, attr, val) + # Save the attributes we just set + module.save() - if 'detached' not in module._class_tags: - parent.children.append(location) - store.update_item(parent, '**replace_user**') + store.update_item(module, user_id) - # 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(location.course_key) - course.tabs.append( - StaticTab( - name=display_name, - url_slug=location.name, + # 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(location.course_key) + course.tabs.append( + StaticTab( + name=display_name, + url_slug=location.name, + ) ) - ) - store.update_item(course, '**replace_user**') + store.update_item(course, user_id) + # parent and publish the item, so it can be accessed + if 'detached' not in module._class_tags: + parent.children.append(location) + store.update_item(parent, user_id) + if publish_item: + store.publish(parent.location, user_id) + elif publish_item: + store.publish(location, user_id) + + # return the published item return store.get_item(location) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py b/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py index e7490e1442..899378b5ef 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py @@ -33,7 +33,7 @@ class PersistentCourseFactory(SplitFactory): # pylint: disable=W0613 @classmethod - def _create(cls, target_class, offering='999', org='testX', user_id='test_user', + def _create(cls, target_class, offering='999', org='testX', user_id=ModuleStoreEnum.UserID.test, master_branch=ModuleStoreEnum.BranchName.draft, **kwargs): modulestore = kwargs.pop('modulestore') @@ -59,7 +59,7 @@ class ItemFactory(SplitFactory): # pylint: disable=W0613 @classmethod def _create(cls, target_class, parent_location, category='chapter', - user_id='test_user', block_id=None, definition_locator=None, force=False, + user_id=ModuleStoreEnum.UserID.test, block_id=None, definition_locator=None, force=False, continue_version=False, **kwargs): """ passes *kwargs* as the new item's field values: diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py index 9485d9323b..8add221bc1 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -116,7 +116,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): self.writable_chapter_location = self.store = self.fake_location = self.xml_chapter_location = None self.course_locations = [] - self.user_id = 0 + self.user_id = ModuleStoreEnum.UserID.test # pylint: disable=invalid-name def _create_course(self, default, course_key): @@ -127,12 +127,12 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): offering = course_key.offering.replace('/', '.') else: offering = course_key.offering - course = self.store.create_course(course_key.org, offering) + course = self.store.create_course(course_key.org, offering, self.user_id) category = self.writable_chapter_location.category block_id = self.writable_chapter_location.name chapter = self.store.create_item( # don't use course_location as it may not be the repr - course.location, category, location=self.writable_chapter_location, block_id=block_id + course.location, category, self.user_id, location=self.writable_chapter_location, block_id=block_id ) if isinstance(course.id, CourseLocator): self.course_locations[self.MONGO_COURSEID] = course.location.version_agnostic() @@ -184,7 +184,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): ] def create_sub_tree(parent, block_info): - block = self.store.create_item(parent.location, category=block_info.category, block_id=block_info.display_name) + block = self.store.create_item(parent.location, block_info.category, self.user_id, block_id=block_info.display_name) for tree in block_info.sub_tree: create_sub_tree(block, tree) # reload the block to update its children field @@ -306,13 +306,13 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): self.assertFalse(course.show_calculator, "Default changed making test meaningless") course.show_calculator = True with self.assertRaises(NotImplementedError): # ensure it doesn't allow writing - self.store.update_item(course, None) + self.store.update_item(course, self.user_id) # now do it for a r/w db course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key) # if following raised, then the test is really a noop, change it self.assertFalse(course.show_calculator, "Default changed making test meaningless") course.show_calculator = True - self.store.update_item(course, None) + self.store.update_item(course, self.user_id) course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key) self.assertTrue(course.show_calculator) @@ -324,8 +324,8 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): self.initdb(default_ms) # r/o try deleting the course (is here to ensure it can't be deleted) with self.assertRaises(NotImplementedError): - self.store.delete_item(self.xml_chapter_location, 13) - self.store.delete_item(self.writable_chapter_location, 9) + self.store.delete_item(self.xml_chapter_location, self.user_id) + self.store.delete_item(self.writable_chapter_location, self.user_id) # verify it's gone with self.assertRaises(ItemNotFoundError): self.store.get_item(self.writable_chapter_location) @@ -367,7 +367,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): xml_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.xml) # the important thing is not which exception it raises but that it raises an exception with self.assertRaises(AttributeError): - xml_store.create_course("org", "course/run", 999) + xml_store.create_course("org", "course/run", self.user_id) @ddt.data('draft', 'split') def test_get_course(self, default_ms): @@ -547,7 +547,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): self.initdb(default_ms) # create an orphan course_id = self.course_locations[self.MONGO_COURSEID].course_key - orphan = self.store.create_item(course_id, 'problem', block_id='orphan') + orphan = self.store.create_item(course_id, 'problem', self.user_id, block_id='orphan') found_orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key) if default_ms == 'split': self.assertEqual(found_orphans, [orphan.location.version_agnostic()]) @@ -561,7 +561,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango): new location for the child """ self.initdb(default_ms) - self.store.create_item(self.course_locations[self.MONGO_COURSEID], 'problem', block_id='orphan') + self.store.create_item(self.course_locations[self.MONGO_COURSEID], 'problem', self.user_id, block_id='orphan') orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key) self.assertEqual(len(orphans), 0, "unexpected orphans: {}".format(orphans)) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py index a2ab88f8d1..e490b404a2 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py @@ -134,6 +134,14 @@ class TestMongoModuleStore(unittest.TestCase): # Destroy the test db. connection.drop_database(DB) + def setUp(self): + # make a copy for convenience + self.connection = TestMongoModuleStore.connection + self.dummy_user = ModuleStoreEnum.UserID.test + + def tearDown(self): + pass + def test_init(self): '''Make sure the db loads''' ids = list(self.connection[DB][COLLECTION].find({}, {'_id': True})) @@ -351,7 +359,7 @@ class TestMongoModuleStore(unittest.TestCase): # set toy course to share the wiki with simple course toy_course = self.draft_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')) toy_course.wiki_slug = 'simple' - self.draft_store.update_item(toy_course) + self.draft_store.update_item(toy_course, ModuleStoreEnum.UserID.test) # now toy_course should not be retrievable with old wiki_slug course_locations = self.draft_store.get_courses_for_wiki('toy') @@ -366,7 +374,7 @@ class TestMongoModuleStore(unittest.TestCase): # configure simple course to use unique wiki_slug. simple_course = self.draft_store.get_course(SlashSeparatedCourseKey('edX', 'simple', '2012_Fall')) simple_course.wiki_slug = 'edX.simple.2012_Fall' - self.draft_store.update_item(simple_course) + self.draft_store.update_item(simple_course, ModuleStoreEnum.UserID.test) # it should be retrievable with its new wiki_slug course_locations = self.draft_store.get_courses_for_wiki('edX.simple.2012_Fall') assert_equals(len(course_locations), 1) @@ -491,11 +499,10 @@ class TestMongoModuleStore(unittest.TestCase): """ course_location = Location('edx', 'direct', '2012_Fall', 'course', 'test_course') chapter_location = Location('edx', 'direct', '2012_Fall', 'chapter', 'test_chapter') - dummy_user = 123 # Create dummy direct only xblocks - self.draft_store.create_and_save_xmodule(course_location, user_id=dummy_user) - self.draft_store.create_and_save_xmodule(chapter_location, user_id=dummy_user) + self.draft_store.create_and_save_xmodule(course_location, user_id=self.dummy_user) + self.draft_store.create_and_save_xmodule(chapter_location, user_id=self.dummy_user) # Check that neither xblock has changes self.assertFalse(self.draft_store.has_changes(course_location)) @@ -506,29 +513,28 @@ class TestMongoModuleStore(unittest.TestCase): Tests that has_changes() only returns true when changes are present """ location = Location('edX', 'changes', '2012_Fall', 'vertical', 'test_vertical') - dummy_user = 123 # Create a dummy component to test against - self.draft_store.create_and_save_xmodule(location, user_id=dummy_user) + self.draft_store.create_and_save_xmodule(location, user_id=self.dummy_user) # Not yet published, so changes are present self.assertTrue(self.draft_store.has_changes(location)) # Publish and verify that there are no unpublished changes - self.draft_store.publish(location, dummy_user) + self.draft_store.publish(location, self.dummy_user) self.assertFalse(self.draft_store.has_changes(location)) # Change the component, then check that there now are changes component = self.draft_store.get_item(location) component.display_name = 'Changed Display Name' - self.draft_store.update_item(component, dummy_user) + self.draft_store.update_item(component, self.dummy_user) self.assertTrue(self.draft_store.has_changes(location)) # Publish and verify again - self.draft_store.publish(location, dummy_user) + self.draft_store.publish(location, self.dummy_user) self.assertFalse(self.draft_store.has_changes(location)) - def _create_test_tree(self, name, user_id=123): + def _create_test_tree(self, name, user_id=None): """ Creates and returns a tree with the following structure: Grandparent @@ -538,6 +544,9 @@ class TestMongoModuleStore(unittest.TestCase): Child Sibling """ + if user_id is None: + user_id = self.dummy_user + locations = { 'grandparent': Location('edX', 'tree', name, 'chapter', 'grandparent'), 'parent_sibling': Location('edX', 'tree', name, 'sequential', 'parent_sibling'), @@ -566,7 +575,6 @@ class TestMongoModuleStore(unittest.TestCase): """ Tests that has_changes() returns true on ancestors when a child is changed """ - dummy_user = 123 locations = self._create_test_tree('has_changes_ancestors') # Verify that there are no unpublished changes @@ -576,7 +584,7 @@ class TestMongoModuleStore(unittest.TestCase): # Change the child child = self.draft_store.get_item(locations['child']) child.display_name = 'Changed Display Name' - self.draft_store.update_item(child, user_id=dummy_user) + self.draft_store.update_item(child, user_id=self.dummy_user) # All ancestors should have changes, but not siblings self.assertTrue(self.draft_store.has_changes(locations['grandparent'])) @@ -586,7 +594,7 @@ class TestMongoModuleStore(unittest.TestCase): self.assertFalse(self.draft_store.has_changes(locations['child_sibling'])) # Publish the unit with changes - self.draft_store.publish(locations['parent'], dummy_user) + self.draft_store.publish(locations['parent'], self.dummy_user) # Verify that there are no unpublished changes for key in locations: @@ -596,7 +604,6 @@ class TestMongoModuleStore(unittest.TestCase): """ Tests that has_changes() returns false after a child is published only if all children are unchanged """ - dummy_user = 123 locations = self._create_test_tree('has_changes_publish_ancestors') # Verify that there are no unpublished changes @@ -608,22 +615,22 @@ class TestMongoModuleStore(unittest.TestCase): child_sibling = self.draft_store.get_item(locations['child_sibling']) child.display_name = 'Changed Display Name' child_sibling.display_name = 'Changed Display Name' - self.draft_store.update_item(child, user_id=dummy_user) - self.draft_store.update_item(child_sibling, user_id=dummy_user) + self.draft_store.update_item(child, user_id=self.dummy_user) + self.draft_store.update_item(child_sibling, user_id=self.dummy_user) # Verify that ancestors have changes self.assertTrue(self.draft_store.has_changes(locations['grandparent'])) self.assertTrue(self.draft_store.has_changes(locations['parent'])) # Publish one child - self.draft_store.publish(locations['child_sibling'], dummy_user) + self.draft_store.publish(locations['child_sibling'], self.dummy_user) # Verify that ancestors still have changes self.assertTrue(self.draft_store.has_changes(locations['grandparent'])) self.assertTrue(self.draft_store.has_changes(locations['parent'])) # Publish the other child - self.draft_store.publish(locations['child'], dummy_user) + self.draft_store.publish(locations['child'], self.dummy_user) # Verify that ancestors now have no changes self.assertFalse(self.draft_store.has_changes(locations['grandparent'])) @@ -634,7 +641,6 @@ class TestMongoModuleStore(unittest.TestCase): Tests that has_changes() returns true for the parent when a child with changes is added and false when that child is removed. """ - dummy_user = 123 locations = self._create_test_tree('has_changes_add_remove_child') # Test that the ancestors don't have changes @@ -643,10 +649,10 @@ class TestMongoModuleStore(unittest.TestCase): # Create a new child and attach it to parent new_child_location = Location('edX', 'tree', 'has_changes_add_remove_child', 'vertical', 'new_child') - self.draft_store.create_and_save_xmodule(new_child_location, user_id=dummy_user) + self.draft_store.create_and_save_xmodule(new_child_location, user_id=self.dummy_user) parent = self.draft_store.get_item(locations['parent']) parent.children += [new_child_location] - self.draft_store.update_item(parent, user_id=dummy_user) + self.draft_store.update_item(parent, user_id=self.dummy_user) # Verify that the ancestors now have changes self.assertTrue(self.draft_store.has_changes(locations['grandparent'])) @@ -655,7 +661,7 @@ class TestMongoModuleStore(unittest.TestCase): # Remove the child from the parent parent = self.draft_store.get_item(locations['parent']) parent.children = [locations['child'], locations['child_sibling']] - self.draft_store.update_item(parent, user_id=dummy_user) + self.draft_store.update_item(parent, user_id=self.dummy_user) # Verify that ancestors now have no changes self.assertFalse(self.draft_store.has_changes(locations['grandparent'])) @@ -665,15 +671,14 @@ class TestMongoModuleStore(unittest.TestCase): """ Tests that has_changes() returns true after editing the child of a vertical (both not direct only categories). """ - dummy_user = 123 parent_location = Location('edX', 'test', 'non_direct_only_children', 'vertical', 'parent') child_location = Location('edX', 'test', 'non_direct_only_children', 'html', 'child') - parent = self.draft_store.create_and_save_xmodule(parent_location, user_id=dummy_user) - child = self.draft_store.create_and_save_xmodule(child_location, user_id=dummy_user) + parent = self.draft_store.create_and_save_xmodule(parent_location, user_id=self.dummy_user) + child = self.draft_store.create_and_save_xmodule(child_location, user_id=self.dummy_user) parent.children += [child_location] - self.draft_store.update_item(parent, user_id=dummy_user) - self.draft_store.publish(parent_location, dummy_user) + self.draft_store.update_item(parent, user_id=self.dummy_user) + self.draft_store.publish(parent_location, self.dummy_user) # Verify that there are no changes self.assertFalse(self.draft_store.has_changes(parent_location)) @@ -681,7 +686,7 @@ class TestMongoModuleStore(unittest.TestCase): # Change the child child.display_name = 'Changed Display Name' - self.draft_store.update_item(child, user_id=123) + self.draft_store.update_item(child, user_id=self.dummy_user) # Verify that both parent and child have changes self.assertTrue(self.draft_store.has_changes(parent_location)) @@ -693,7 +698,7 @@ class TestMongoModuleStore(unittest.TestCase): """ create_user = 123 edit_user = 456 - locations = self._create_test_tree('update_edit_info_ancestors', create_user) + locations =self._create_test_tree('update_edit_info_ancestors', create_user) def check_node(location_key, after, before, edited_by, subtree_after, subtree_before, subtree_by): """ @@ -739,24 +744,23 @@ class TestMongoModuleStore(unittest.TestCase): Tests that edited_on and edited_by are set correctly during an update """ location = Location('edX', 'editInfoTest', '2012_Fall', 'html', 'test_html') - dummy_user = 123 # Create a dummy component to test against - self.draft_store.create_and_save_xmodule(location, user_id=dummy_user) + self.draft_store.create_and_save_xmodule(location, user_id=self.dummy_user) # Store the current edit time and verify that dummy_user created the component component = self.draft_store.get_item(location) - self.assertEqual(component.edited_by, dummy_user) + self.assertEqual(component.edited_by, self.dummy_user) old_edited_on = component.edited_on # Change the component component.display_name = component.display_name + ' Changed' - self.draft_store.update_item(component, dummy_user) + self.draft_store.update_item(component, self.dummy_user) updated_component = self.draft_store.get_item(location) # Verify the ordering of edit times and that dummy_user made the edit self.assertLess(old_edited_on, updated_component.edited_on) - self.assertEqual(updated_component.edited_by, dummy_user) + self.assertEqual(updated_component.edited_by, self.dummy_user) def test_update_published_info(self): """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py index 46c9c60e31..e573acc78e 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py @@ -5,6 +5,7 @@ Tests for split_migrator import uuid import random import mock +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.mongo.base import MongoRevisionKey from xmodule.modulestore.loc_mapper_store import LocMapperStore from xmodule.modulestore.split_migrator import SplitMigrator @@ -63,7 +64,7 @@ class TestMigration(SplitWMongoCourseBoostrapper): self.create_random_units(False, both_vert_loc) draft_both = self.draft_mongo.get_item(both_vert_loc) draft_both.display_name = 'Both vertical renamed' - self.draft_mongo.update_item(draft_both) + self.draft_mongo.update_item(draft_both, ModuleStoreEnum.UserID.test) self.create_random_units(True, both_vert_loc) # vertical in draft only (x2) draft_vert_loc = self.old_course_key.make_usage_key('vertical', uuid.uuid4().hex) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py index 3345827215..57e5447186 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py @@ -441,6 +441,7 @@ class SplitModuleTest(unittest.TestCase): } }, } + @staticmethod def bootstrapDB(): ''' @@ -489,6 +490,9 @@ class SplitModuleTest(unittest.TestCase): destination = CourseLocator(org="testx", offering="wonderful", branch=BRANCH_NAME_PUBLISHED) split_store.xblock_publish("test@edx.org", to_publish, destination, [to_publish], None) + def setUp(self): + self.user_id = random.getrandbits(32) + def tearDown(self): """ Clear persistence between each test. @@ -817,7 +821,7 @@ class SplitModuleItemTests(SplitModuleTest): draft_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_DRAFT) published_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_PUBLISHED) head = draft_course.make_usage_key('course', 'head12345') - dummy_user = 'testUser' + dummy_user = ModuleStoreEnum.UserID.test # Not yet published, so changes are present self.assertTrue(modulestore().has_changes(head)) @@ -1217,7 +1221,7 @@ class TestItemCrud(SplitModuleTest): problem.max_attempts = 4 problem.save() # decache above setting into the kvs - updated_problem = modulestore().update_item(problem, '**replace_user**') + updated_problem = modulestore().update_item(problem, self.user_id) # check that course version changed and course's previous is the other one self.assertEqual(updated_problem.definition_locator.definition_id, pre_def_id) self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid) @@ -1232,7 +1236,7 @@ class TestItemCrud(SplitModuleTest): history_info = modulestore().get_course_history_info(current_course.location) self.assertEqual(history_info['previous_version'], pre_version_guid) - self.assertEqual(history_info['edited_by'], "**replace_user**") + self.assertEqual(history_info['edited_by'], self.user_id) def test_update_children(self): """ @@ -1249,7 +1253,7 @@ class TestItemCrud(SplitModuleTest): self.assertGreater(len(block.children), 0, "meaningless test") moved_child = block.children.pop() block.save() # decache model changes - updated_problem = modulestore().update_item(block, '**replace_user**') + updated_problem = modulestore().update_item(block, self.user_id) # check that course version changed and course's previous is the other one self.assertEqual(updated_problem.definition_locator.definition_id, pre_def_id) self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid) @@ -1258,7 +1262,7 @@ class TestItemCrud(SplitModuleTest): locator = locator.course_key.make_usage_key('Chapter', "chapter1") other_block = modulestore().get_item(locator) other_block.children.append(moved_child) - other_updated = modulestore().update_item(other_block, '**replace_user**') + other_updated = modulestore().update_item(other_block, self.user_id) self.assertIn(moved_child.version_agnostic(), version_agnostic(other_updated.children)) def test_update_definition(self): @@ -1274,7 +1278,7 @@ class TestItemCrud(SplitModuleTest): block.grading_policy['GRADER'][0]['min_count'] = 13 block.save() # decache model changes - updated_block = modulestore().update_item(block, '**replace_user**') + updated_block = modulestore().update_item(block, self.user_id) self.assertNotEqual(updated_block.definition_locator.definition_id, pre_def_id) self.assertNotEqual(updated_block.location.version_guid, pre_version_guid) @@ -1320,7 +1324,7 @@ class TestItemCrud(SplitModuleTest): block.advertised_start = "Soon" block.save() # decache model changes - updated_block = modulestore().update_item(block, "**replace_user**") + updated_block = modulestore().update_item(block, self.user_id) self.assertNotEqual(updated_block.definition_locator.definition_id, pre_def_id) self.assertNotEqual(updated_block.location.version_guid, pre_version_guid) self.assertEqual(updated_block.grading_policy['GRADER'][0]['min_count'], 13) @@ -1330,13 +1334,13 @@ class TestItemCrud(SplitModuleTest): def test_delete_item(self): course = self.create_course_for_deletion() with self.assertRaises(ValueError): - modulestore().delete_item(course.location, 'deleting_user') + modulestore().delete_item(course.location, self.user_id) reusable_location = course.id.version_agnostic().for_branch(BRANCH_NAME_DRAFT) # delete a leaf problems = modulestore().get_items(reusable_location, category='problem') locn_to_del = problems[0].location - new_course_loc = modulestore().delete_item(locn_to_del, 'deleting_user') + new_course_loc = modulestore().delete_item(locn_to_del, self.user_id) deleted = locn_to_del.version_agnostic() self.assertFalse(modulestore().has_item(deleted)) with self.assertRaises(VersionConflictError): @@ -1347,7 +1351,7 @@ class TestItemCrud(SplitModuleTest): # delete a subtree nodes = modulestore().get_items(reusable_location, category='chapter') - new_course_loc = modulestore().delete_item(nodes[0].location, 'deleting_user') + new_course_loc = modulestore().delete_item(nodes[0].location, self.user_id) # check subtree def check_subtree(node): @@ -1582,7 +1586,6 @@ class TestPublish(SplitModuleTest): """ def setUp(self): SplitModuleTest.setUp(self) - self.user = random.getrandbits(32) def tearDown(self): SplitModuleTest.tearDown(self) @@ -1597,14 +1600,14 @@ class TestPublish(SplitModuleTest): chapter1 = source_course.make_usage_key('chapter', 'chapter1') chapter2 = source_course.make_usage_key('chapter', 'chapter2') chapter3 = source_course.make_usage_key('chapter', 'chapter3') - modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2, chapter3]) + modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2, chapter3]) expected = [head.block_id, chapter1.block_id] self._check_course( source_course, dest_course, expected, [chapter2.block_id, chapter3.block_id, "problem1", "problem3_2"] ) # add a child under chapter1 new_module = modulestore().create_item( - chapter1, "sequential", self.user, + chapter1, "sequential", self.user_id, fields={'display_name': 'new sequential'}, ) # remove chapter1 from expected b/c its pub'd version != the source anymore since source changed @@ -1613,7 +1616,7 @@ class TestPublish(SplitModuleTest): with self.assertRaises(ItemNotFoundError): modulestore().get_item(new_module.location.map_into_course(dest_course)) # publish it - modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location], None) + modulestore().xblock_publish(self.user_id, source_course, dest_course, [new_module.location], None) expected.append(new_module.location.block_id) # check that it is in the published course and that its parent is the chapter pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course)) @@ -1622,10 +1625,10 @@ class TestPublish(SplitModuleTest): ) # ensure intentionally orphaned blocks work (e.g., course_info) new_module = modulestore().create_item( - source_course, "course_info", self.user, block_id="handouts" + source_course, "course_info", self.user_id, block_id="handouts" ) # publish it - modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location], None) + modulestore().xblock_publish(self.user_id, source_course, dest_course, [new_module.location], None) expected.append(new_module.location.block_id) # check that it is in the published course (no error means it worked) pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course)) @@ -1644,15 +1647,15 @@ class TestPublish(SplitModuleTest): chapter3 = source_course.make_usage_key('chapter', 'chapter3') problem1 = source_course.make_usage_key('problem', 'problem1') with self.assertRaises(ItemNotFoundError): - modulestore().xblock_publish(self.user, source_course, destination_course, [chapter3], None) + modulestore().xblock_publish(self.user_id, source_course, destination_course, [chapter3], None) # publishing into a new branch w/o publishing the root destination_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_PUBLISHED) with self.assertRaises(ItemNotFoundError): - modulestore().xblock_publish(self.user, source_course, destination_course, [chapter3], None) + modulestore().xblock_publish(self.user_id, source_course, destination_course, [chapter3], None) # publishing a subdag w/o the parent already in course - modulestore().xblock_publish(self.user, source_course, destination_course, [head], [chapter3]) + modulestore().xblock_publish(self.user_id, source_course, destination_course, [head], [chapter3]) with self.assertRaises(ItemNotFoundError): - modulestore().xblock_publish(self.user, source_course, destination_course, [problem1], []) + modulestore().xblock_publish(self.user_id, source_course, destination_course, [problem1], []) def test_move_delete(self): """ @@ -1663,7 +1666,7 @@ class TestPublish(SplitModuleTest): head = source_course.make_usage_key('course', "head12345") chapter2 = source_course.make_usage_key('chapter', 'chapter2') problem1 = source_course.make_usage_key('problem', 'problem1') - modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2]) + modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2]) expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"] self._check_course(source_course, dest_course, expected, ["chapter2"]) # now move problem1 and delete problem3_2 @@ -1671,8 +1674,8 @@ class TestPublish(SplitModuleTest): chapter3 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter3")) chapter1.children.append(problem1) chapter3.children.remove(problem1.map_into_course(chapter3.location.course_key)) - modulestore().delete_item(source_course.make_usage_key("problem", "problem3_2"), self.user) - modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2]) + modulestore().delete_item(source_course.make_usage_key("problem", "problem3_2"), self.user_id) + modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2]) expected = ["head12345", "chapter1", "chapter3", "problem1"] self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"]) @@ -1681,7 +1684,7 @@ class TestPublish(SplitModuleTest): Check that the course has the expected blocks and does not have the unexpected blocks """ history_info = modulestore().get_course_history_info(dest_course_loc) - self.assertEqual(history_info['edited_by'], self.user) + self.assertEqual(history_info['edited_by'], self.user_id) for expected in expected_blocks: # since block_type has no impact on identity, we can just provide an empty string source = modulestore().get_item(source_course_loc.make_usage_key("", expected)) @@ -1690,8 +1693,8 @@ class TestPublish(SplitModuleTest): self.assertEqual(source.category, pub_copy.category) self.assertEqual(source.update_version, pub_copy.update_version) self.assertEqual( - self.user, pub_copy.edited_by, - "{} edited_by {} not {}".format(pub_copy.location, pub_copy.edited_by, self.user) + self.user_id, pub_copy.edited_by, + "{} edited_by {} not {}".format(pub_copy.location, pub_copy.edited_by, self.user_id) ) for field in source.fields.values(): if field.name == 'children': diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_w_old_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_w_old_mongo.py index 1b6c72a3fa..cc13bd838d 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_w_old_mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_w_old_mongo.py @@ -10,7 +10,6 @@ from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore from xmodule.modulestore.mongo import MongoModuleStore, DraftMongoModuleStore from xmodule.modulestore.mongo.draft import DIRECT_ONLY_CATEGORIES from xmodule.modulestore import ModuleStoreEnum -from mock import Mock class SplitWMongoCourseBoostrapper(unittest.TestCase): @@ -138,6 +137,6 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase): self.split_mongo.create_course( self.split_course_key.org, self.split_course_key.offering, self.userid, fields=fields, root_block_id='runid' ) - old_course = self.old_mongo.create_course(self.split_course_key.org, 'test_course/runid', fields=fields) + old_course = self.old_mongo.create_course(self.split_course_key.org, 'test_course/runid', self.userid, fields=fields) self.old_course_key = old_course.id self.runtime = old_course.runtime diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index d1d4636483..61e71b70db 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -17,6 +17,7 @@ import xblock from xmodule.tabs import CourseTabList from xmodule.modulestore.exceptions import InvalidLocationError from xmodule.modulestore.mongo.base import MongoRevisionKey +from xmodule.modulestore import ModuleStoreEnum log = logging.getLogger(__name__) @@ -159,165 +160,167 @@ def import_from_xml( # of course modules. It will be left as a TBD to implement that # method on XmlModuleStore. course_items = [] - for course_key in xml_module_store.modules.keys(): - if target_course_id is not None: - dest_course_id = target_course_id - else: - dest_course_id = course_key + with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + for course_key in xml_module_store.modules.keys(): - # Creates a new course if it doesn't already exist - if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True): - try: - store.create_course(dest_course_id.org, dest_course_id.offering) - except InvalidLocationError: - # course w/ same org and course exists - log.debug( - "Skipping import of course with id, {0}," - "since it collides with an existing one".format(dest_course_id) - ) - continue + if target_course_id is not None: + dest_course_id = target_course_id + else: + dest_course_id = course_key - with store.bulk_write_operations(dest_course_id): - course_data_path = None - - if verbose: - log.debug("Scanning {0} for course module...".format(course_key)) - - # Quick scan to get course module as we need some info from there. - # Also we need to make sure that the course module is committed - # first into the store - for module in xml_module_store.modules[course_key].itervalues(): - if module.scope_ids.block_type == 'course': - course_data_path = path(data_dir) / module.data_dir - - log.debug(u'======> IMPORTING course {course_key}'.format( - course_key=course_key, - )) - - if not do_import_static: - # for old-style xblock where this was actually linked to kvs - module.static_asset_path = module.data_dir - module.save() - log.debug('course static_asset_path={path}'.format( - path=module.static_asset_path - )) - - log.debug('course data_dir={0}'.format(module.data_dir)) - - course = _import_module_and_update_references( - module, store, user_id, - course_key, - dest_course_id, - do_import_static=do_import_static + # Creates a new course if it doesn't already exist + if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True): + try: + store.create_course(dest_course_id.org, dest_course_id.offering, user_id) + except InvalidLocationError: + # course w/ same org and course exists + log.debug( + "Skipping import of course with id, {0}," + "since it collides with an existing one".format(dest_course_id) ) - - for entry in course.pdf_textbooks: - for chapter in entry.get('chapters', []): - if StaticContent.is_c4x_path(chapter.get('url', '')): - asset_key = StaticContent.get_location_from_path(chapter['url']) - chapter['url'] = StaticContent.get_static_path_from_location(asset_key) - - # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. - # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default - # values then remap it so that the wiki does not point to the old wiki. - if course_key != course.id: - original_unique_wiki_slug = u'{0}.{1}.{2}'.format( - course_key.org, - course_key.course, - course_key.run - ) - if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course: - course.wiki_slug = u'{0}.{1}.{2}'.format( - course.id.org, - course.id.course, - course.id.run, - ) - - # cdodge: more hacks (what else). Seems like we have a - # problem when importing a course (like 6.002) which - # does not have any tabs defined in the policy file. - # The import goes fine and then displays fine in LMS, - # but if someone tries to add a new tab in the CMS, then - # the LMS barfs because it expects that -- if there are - # *any* tabs -- then there at least needs to be - # some predefined ones - if course.tabs is None or len(course.tabs) == 0: - CourseTabList.initialize_default(course) - - store.update_item(course, user_id) - - course_items.append(course) - break - - # TODO: shouldn't this raise an exception if course wasn't found? - - # then import all the static content - if static_content_store is not None and do_import_static: - # first pass to find everything in /static/ - import_static_content( - course_data_path, static_content_store, - dest_course_id, subpath='static', verbose=verbose - ) - - elif verbose and not do_import_static: - log.debug( - "Skipping import of static content, " - "since do_import_static={0}".format(do_import_static) - ) - - # no matter what do_import_static is, import "static_import" directory - - # This is needed because the "about" pages (eg "overview") are - # loaded via load_extra_content, and do not inherit the lms - # metadata from the course module, and thus do not get - # "static_content_store" properly defined. Static content - # referenced in those extra pages thus need to come through the - # c4x:// contentstore, unfortunately. Tell users to copy that - # content into the "static_import" subdir. - - simport = 'static_import' - if os.path.exists(course_data_path / simport): - import_static_content( - course_data_path, static_content_store, - dest_course_id, subpath=simport, verbose=verbose - ) - - # now loop through all the modules - for module in xml_module_store.modules[course_key].itervalues(): - if module.scope_ids.block_type == 'course': - # we've already saved the course module up at the top - # of the loop so just skip over it in the inner loop continue - if verbose: - log.debug('importing module location {loc}'.format( - loc=module.location - )) + with store.bulk_write_operations(dest_course_id): + course_data_path = None - _import_module_and_update_references( - module, store, + if verbose: + log.debug("Scanning {0} for course module...".format(course_key)) + + # Quick scan to get course module as we need some info from there. + # Also we need to make sure that the course module is committed + # first into the store + for module in xml_module_store.modules[course_key].itervalues(): + if module.scope_ids.block_type == 'course': + course_data_path = path(data_dir) / module.data_dir + + log.debug(u'======> IMPORTING course {course_key}'.format( + course_key=course_key, + )) + + if not do_import_static: + # for old-style xblock where this was actually linked to kvs + module.static_asset_path = module.data_dir + module.save() + log.debug('course static_asset_path={path}'.format( + path=module.static_asset_path + )) + + log.debug('course data_dir={0}'.format(module.data_dir)) + + course = _import_module_and_update_references( + module, store, user_id, + course_key, + dest_course_id, + do_import_static=do_import_static + ) + + for entry in course.pdf_textbooks: + for chapter in entry.get('chapters', []): + if StaticContent.is_c4x_path(chapter.get('url', '')): + asset_key = StaticContent.get_location_from_path(chapter['url']) + chapter['url'] = StaticContent.get_static_path_from_location(asset_key) + + # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. + # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default + # values then remap it so that the wiki does not point to the old wiki. + if course_key != course.id: + original_unique_wiki_slug = u'{0}.{1}.{2}'.format( + course_key.org, + course_key.course, + course_key.run + ) + if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course: + course.wiki_slug = u'{0}.{1}.{2}'.format( + course.id.org, + course.id.course, + course.id.run, + ) + + # cdodge: more hacks (what else). Seems like we have a + # problem when importing a course (like 6.002) which + # does not have any tabs defined in the policy file. + # The import goes fine and then displays fine in LMS, + # but if someone tries to add a new tab in the CMS, then + # the LMS barfs because it expects that -- if there are + # *any* tabs -- then there at least needs to be + # some predefined ones + if course.tabs is None or len(course.tabs) == 0: + CourseTabList.initialize_default(course) + + store.update_item(course, user_id) + + course_items.append(course) + break + + # TODO: shouldn't this raise an exception if course wasn't found? + + # then import all the static content + if static_content_store is not None and do_import_static: + # first pass to find everything in /static/ + import_static_content( + course_data_path, static_content_store, + dest_course_id, subpath='static', verbose=verbose + ) + + elif verbose and not do_import_static: + log.debug( + "Skipping import of static content, " + "since do_import_static={0}".format(do_import_static) + ) + + # no matter what do_import_static is, import "static_import" directory + + # This is needed because the "about" pages (eg "overview") are + # loaded via load_extra_content, and do not inherit the lms + # metadata from the course module, and thus do not get + # "static_content_store" properly defined. Static content + # referenced in those extra pages thus need to come through the + # c4x:// contentstore, unfortunately. Tell users to copy that + # content into the "static_import" subdir. + + simport = 'static_import' + if os.path.exists(course_data_path / simport): + import_static_content( + course_data_path, static_content_store, + dest_course_id, subpath=simport, verbose=verbose + ) + + # now loop through all the modules + for module in xml_module_store.modules[course_key].itervalues(): + if module.scope_ids.block_type == 'course': + # we've already saved the course module up at the top + # of the loop so just skip over it in the inner loop + continue + + if verbose: + log.debug('importing module location {loc}'.format( + loc=module.location + )) + + _import_module_and_update_references( + module, store, + user_id, + course_key, + dest_course_id, + do_import_static=do_import_static, + runtime=course.runtime + ) + + # finally, publish the course + store.publish(course.location, user_id) + + # now import any DRAFT items + _import_course_draft( + xml_module_store, + store, user_id, + course_data_path, course_key, dest_course_id, - do_import_static=do_import_static, - runtime=course.runtime + course.runtime ) - # finally, publish the course - store.publish(course.location, user_id) - - # now import any DRAFT items - _import_course_draft( - xml_module_store, - store, - user_id, - course_data_path, - course_key, - dest_course_id, - course.runtime - ) - return xml_module_store, course_items diff --git a/common/lib/xmodule/xmodule/split_test_module.py b/common/lib/xmodule/xmodule/split_test_module.py index 40da667840..9bd5810efe 100644 --- a/common/lib/xmodule/xmodule/split_test_module.py +++ b/common/lib/xmodule/xmodule/split_test_module.py @@ -559,7 +559,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes changed = True if changed: - # request does not have a user attribute, so pass None for user. + # TODO user.id - to be fixed by Publishing team self.system.modulestore.update_item(self, None) return Response() diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index 88a2cb1b5c..26e23207c9 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -283,7 +283,7 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto Save module with updated metadata to database." """ self.save() - self.runtime.modulestore.update_item(self, user.id if user else None) + self.runtime.modulestore.update_item(self, user.id) @property def editable_metadata_fields(self): diff --git a/lms/djangoapps/branding/tests.py b/lms/djangoapps/branding/tests.py index 0a623d0868..5faab59a7a 100644 --- a/lms/djangoapps/branding/tests.py +++ b/lms/djangoapps/branding/tests.py @@ -28,12 +28,13 @@ class AnonymousIndexPageTest(ModuleStoreTestCase): Tests that anonymous users can access the '/' page, Need courses with start date """ def setUp(self): - self.store = modulestore() + super(AnonymousIndexPageTest, self).setUp() self.factory = RequestFactory() - self.course = CourseFactory.create() - self.course.days_early_for_beta = 5 - self.course.enrollment_start = datetime.datetime.now(UTC) + datetime.timedelta(days=3) - self.store.update_item(self.course, '**replace_user**') + self.course = CourseFactory.create( + days_early_for_beta=5, + enrollment_start=datetime.datetime.now(UTC)+datetime.timedelta(days=3), + user_id=self.user.id, + ) @override_settings(FEATURES=FEATURES_WITH_STARTDATE) def test_none_user_index_access_with_startdate_fails(self): diff --git a/lms/djangoapps/courseware/features/annotatable.py b/lms/djangoapps/courseware/features/annotatable.py index bf004ffdd3..829e939efa 100644 --- a/lms/djangoapps/courseware/features/annotatable.py +++ b/lms/djangoapps/courseware/features/annotatable.py @@ -3,7 +3,7 @@ import textwrap from lettuce import world, steps from nose.tools import assert_in, assert_equals, assert_true -from common import i_am_registered_for_the_course, visit_scenario_item, publish +from common import i_am_registered_for_the_course, visit_scenario_item DATA_TEMPLATE = textwrap.dedent("""\ @@ -78,9 +78,6 @@ class AnnotatableSteps(object): display_name="Test Annotation Module", data=DATA_TEMPLATE.format("\n".join(ANNOTATION_TEMPLATE.format(i) for i in xrange(count))) ) - - publish(world.scenario_dict['ANNOTATION_VERTICAL'].location) - self.annotations_count = count def view_component(self, step): @@ -125,7 +122,6 @@ class AnnotatableSteps(object): ) ) ) - publish(world.scenario_dict['ANNOTATION_VERTICAL'].location) def click_reply(self, step, problem): r"""I click "Reply to annotation" on passage (?P\d+)$""" diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 5dbafa1793..2596be9c73 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -134,10 +134,6 @@ def section_location(course_num): return world.scenario_dict['SECTION'].location.replace(course=course_num) -def publish(location): - modulestore().publish(location, '**replace_user**') - - def visit_scenario_item(item_key): """ Go to the courseware page containing the item stored in `world.scenario_dict` diff --git a/lms/djangoapps/courseware/features/conditional.py b/lms/djangoapps/courseware/features/conditional.py index 5cca107a6a..74ada169f3 100644 --- a/lms/djangoapps/courseware/features/conditional.py +++ b/lms/djangoapps/courseware/features/conditional.py @@ -4,7 +4,7 @@ from lettuce import world, steps from nose.tools import assert_in, assert_true # pylint: disable=no-name-in-module -from common import i_am_registered_for_the_course, visit_scenario_item, publish +from common import i_am_registered_for_the_course, visit_scenario_item from problems_setup import add_problem_to_course, answer_problem @steps @@ -67,9 +67,6 @@ class ConditionalSteps(object): data='
Hidden Contents

' ) - publish(world.scenario_dict['VERTICAL'].location) - - def setup_problem_attempts(self, step, not_attempted=None): r'that the conditioned problem has (?Pnot )?been attempted$' visit_scenario_item('CONDITION_SOURCE') diff --git a/lms/djangoapps/courseware/features/gst.py b/lms/djangoapps/courseware/features/gst.py index d46e9d430a..a8693ead30 100644 --- a/lms/djangoapps/courseware/features/gst.py +++ b/lms/djangoapps/courseware/features/gst.py @@ -2,7 +2,7 @@ from lettuce import world, steps from nose.tools import assert_in, assert_equals, assert_true -from common import i_am_registered_for_the_course, visit_scenario_item, publish +from common import i_am_registered_for_the_course, visit_scenario_item from problems_setup import add_problem_to_course, answer_problem diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py index 933b85d816..2ecf952841 100644 --- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py +++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py @@ -18,6 +18,7 @@ from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MONGO_MODULESTORE +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -56,7 +57,7 @@ class CommandsTestBase(TestCase): courses = store.get_courses() # NOTE: if xml store owns these, it won't import them into mongo if SlashSeparatedCourseKey.from_deprecated_string(TEST_COURSE_ID) not in [c.id for c in courses]: - import_from_xml(store, "**replace_user**", DATA_DIR, ['toy', 'simple']) + import_from_xml(store, ModuleStoreEnum.UserID.mgmt_command, DATA_DIR, ['toy', 'simple']) return [course.id for course in store.get_courses()] diff --git a/lms/djangoapps/courseware/tests/__init__.py b/lms/djangoapps/courseware/tests/__init__.py index ef88da053d..e0a0d0ee6a 100644 --- a/lms/djangoapps/courseware/tests/__init__.py +++ b/lms/djangoapps/courseware/tests/__init__.py @@ -132,6 +132,7 @@ class BaseTestXmodule(ModuleStoreTestCase): self.assertTrue(all(self.login_statuses)) def setUp(self): + super(BaseTestXmodule, self).setUp() self.setup_course() self.initialize_module(metadata=self.METADATA, data=self.DATA) diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index fb4fc60d64..8c76b6844f 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -67,7 +67,7 @@ class LoginEnrollmentTestCase(TestCase): self.email = 'foo@test.com' self.password = 'bar' self.username = 'test' - self.create_account(self.username, + self.user = self.create_account(self.username, self.email, self.password) self.activate_user(self.email) self.login(self.email, self.password) @@ -107,7 +107,9 @@ class LoginEnrollmentTestCase(TestCase): data = json.loads(resp.content) self.assertEqual(data['success'], True) # Check both that the user is created, and inactive - self.assertFalse(User.objects.get(email=email).is_active) + user = User.objects.get(email=email) + self.assertFalse(user.is_active) + return user def activate_user(self, email): """ diff --git a/lms/djangoapps/courseware/tests/test_lti_integration.py b/lms/djangoapps/courseware/tests/test_lti_integration.py index edea26e3e5..e634e460ba 100644 --- a/lms/djangoapps/courseware/tests/test_lti_integration.py +++ b/lms/djangoapps/courseware/tests/test_lti_integration.py @@ -10,7 +10,8 @@ from django.test.utils import override_settings from django.core.urlresolvers import reverse from django.conf import settings -from xmodule.modulestore.mongo.base import MongoRevisionKey +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.x_module import STUDENT_VIEW @@ -160,7 +161,8 @@ class TestLTIModuleListing(ModuleStoreTestCase): parent_location=self.section2.location, display_name="lti draft", category="lti", - location=self.course.id.make_usage_key('lti', 'lti_published').replace(revision=MongoRevisionKey.draft), + location=self.course.id.make_usage_key('lti', 'lti_published'), + publish_item=False, ) def expected_handler_url(self, handler): diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py index 380572d226..0b5ee2e19c 100644 --- a/lms/djangoapps/courseware/tests/test_submitting_problems.py +++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py @@ -19,8 +19,6 @@ from django.test.utils import override_settings from courseware import grades from courseware.models import StudentModule -from xmodule.modulestore.django import modulestore - #import factories and parent testcase modules from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -46,7 +44,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): def setUp(self): - super(TestSubmittingProblems, self).setUp() + super(TestSubmittingProblems, self).setUp(create_user=False) # Create course self.course = CourseFactory.create(display_name=self.COURSE_NAME, number=self.COURSE_SLUG) assert self.course, "Couldn't load course %r" % self.COURSE_NAME @@ -64,7 +62,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Re-fetch the course from the database so that the object being dealt with has everything added to it. """ - self.course = modulestore().get_course(self.course.id) + self.course = self.store.get_course(self.course.id) def problem_location(self, problem_url_name): """ @@ -230,8 +228,7 @@ class TestCourseGrader(TestSubmittingProblems): """ self.course.grading_policy = grading_policy - store = modulestore() - store.update_item(self.course, '**replace_user**') + self.update_course(self.course, self.student_user.id) self.refresh_course() def get_grade_summary(self): diff --git a/lms/djangoapps/courseware/tests/test_video_handlers.py b/lms/djangoapps/courseware/tests/test_video_handlers.py index 77670568bb..99939aa37e 100644 --- a/lms/djangoapps/courseware/tests/test_video_handlers.py +++ b/lms/djangoapps/courseware/tests/test_video_handlers.py @@ -12,6 +12,7 @@ from webob import Request from xmodule.contentstore.content import StaticContent from xmodule.contentstore.django import contentstore from xmodule.modulestore.django import modulestore +from xmodule.modulestore import ModuleStoreEnum from xmodule.x_module import STUDENT_VIEW from . import BaseTestXmodule from .test_video_xml import SOURCE_XML @@ -411,7 +412,8 @@ class TestTranscriptTranslationGetDispatch(TestVideo): self.course.static_asset_path = 'dummy/static' self.course.save() store = modulestore() - store.update_item(self.course, 'OEoXaMPEzfM') + with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id): + store.update_item(self.course, self.user.id) # Test youtube style en request = Request.blank('/translation/en?videoId=12345') diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 674d3b32f8..92ebaf6867 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -110,6 +110,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): return super(TestViewAuth, self).login(user.email, 'test') def setUp(self): + super(TestViewAuth, self).setUp() self.course = CourseFactory.create(number='999', display_name='Robot_Super_Course') self.courseware_chapter = ItemFactory.create(display_name='courseware') @@ -296,8 +297,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow - self.course = self.update_course(self.course) - self.test_course = self.update_course(self.test_course) + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) self.assertFalse(self.course.has_started()) self.assertFalse(self.test_course.has_started()) @@ -321,8 +322,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow - self.course = self.update_course(self.course) - self.test_course = self.update_course(self.test_course) + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) self.login(self.instructor_user) # Enroll in the classes---can't see courseware otherwise. @@ -345,8 +346,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.course.start = tomorrow self.test_course.start = tomorrow - self.course = self.update_course(self.course) - self.test_course = self.update_course(self.test_course) + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) self.login(self.global_staff_user) self.enroll(self.course, True) @@ -373,8 +374,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # test_course course's has self.test_course.enrollment_start = yesterday self.test_course.enrollment_end = tomorrow - self.course = self.update_course(self.course) - self.test_course = self.update_course(self.test_course) + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) # First, try with an enrolled student self.login(self.unenrolled_user) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 3a4f73eaee..e3e13ef9a9 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -142,9 +142,8 @@ class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase): super(TestMongoCoursesLoad, self).setUp() self.setup_user() - # Import the toy course into a Mongo-backed modulestore - self.store = modulestore() - import_from_xml(self.store, "**replace_user**", TEST_DATA_DIR, ['toy']) + # Import the toy course + import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy']) @mock.patch('xmodule.course_module.requests.get') def test_toy_textbooks_loads(self, mock_get): diff --git a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py index f3fb82b5da..a64a9c89cb 100644 --- a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py +++ b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py @@ -15,10 +15,9 @@ from django.core.management.base import CommandError from django.test.utils import override_settings from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE -from xmodule.contentstore.django import contentstore +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from opaque_keys.edx.locations import SlashSeparatedCourseKey -from xmodule.modulestore.store_utilities import delete_course from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase import dashboard.git_import as git_import from dashboard.git_import import GitImportError @@ -161,9 +160,7 @@ class TestGitAddCourse(ModuleStoreTestCase): self.TEST_BRANCH) # Delete to test branching back to master - delete_course(def_ms, contentstore(), - self.TEST_BRANCH_COURSE, - True) + def_ms.delete_course(self.TEST_BRANCH_COURSE, ModuleStoreEnum.UserID.test) self.assertIsNone(def_ms.get_course(self.TEST_BRANCH_COURSE)) git_import.add_repo(self.TEST_REPO, repo_dir / 'edx4edx_lite', diff --git a/lms/djangoapps/dashboard/sysadmin.py b/lms/djangoapps/dashboard/sysadmin.py index 79f790ac04..dfb1879108 100644 --- a/lms/djangoapps/dashboard/sysadmin.py +++ b/lms/djangoapps/dashboard/sysadmin.py @@ -38,10 +38,8 @@ from external_auth.models import ExternalAuthMap from external_auth.views import generate_password from student.models import CourseEnrollment, UserProfile, Registration import track.views -from xmodule.contentstore.django import contentstore from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore -from xmodule.modulestore.store_utilities import delete_course from xmodule.modulestore.xml import XMLModuleStore from opaque_keys.edx.locations import SlashSeparatedCourseKey @@ -591,9 +589,7 @@ class Courses(SysadminDashboardView): elif course_found and not is_xml_course: # delete course that is stored with mongodb backend - content_store = contentstore() - commit = True - delete_course(self.def_ms, content_store, course.id, commit) + self.def_ms.delete_course(course.id, request.user.id) # don't delete user permission groups, though self.msg += \ u"{0} {1} = {2} ({3})".format( diff --git a/lms/djangoapps/dashboard/tests/test_sysadmin.py b/lms/djangoapps/dashboard/tests/test_sysadmin.py index df89c210fe..6f75c7a961 100644 --- a/lms/djangoapps/dashboard/tests/test_sysadmin.py +++ b/lms/djangoapps/dashboard/tests/test_sysadmin.py @@ -54,7 +54,7 @@ class SysadminBaseTestCase(ModuleStoreTestCase): def setUp(self): """Setup test case by adding primary user.""" - super(SysadminBaseTestCase, self).setUp() + super(SysadminBaseTestCase, self).setUp(create_user=False) self.user = UserFactory.create(username='test_user', email='test_user+sysadmin@edx.org', password='foo') @@ -129,6 +129,9 @@ class TestSysadmin(SysadminBaseTestCase): response = self.client.get(reverse(view)) self.assertEqual(response.status_code, 302) + self.user.is_staff = False + self.user.save() + logged_in = self.client.login(username=self.user.username, password='foo') self.assertTrue(logged_in) diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py index b77ee1f11c..f4cb6d2878 100644 --- a/lms/djangoapps/django_comment_client/base/tests.py +++ b/lms/djangoapps/django_comment_client/base/tests.py @@ -41,7 +41,7 @@ class ViewsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin): # Patching the ENABLE_DISCUSSION_SERVICE value affects the contents of urls.py, # so we need to call super.setUp() which reloads urls.py (because # of the UrlResetMixin) - super(ViewsTestCase, self).setUp() + super(ViewsTestCase, self).setUp(create_user=False) # create a course self.course = CourseFactory.create(org='MITx', course='999', diff --git a/lms/djangoapps/instructor_task/tests/test_base.py b/lms/djangoapps/instructor_task/tests/test_base.py index a9881ccec5..70372ce96b 100644 --- a/lms/djangoapps/instructor_task/tests/test_base.py +++ b/lms/djangoapps/instructor_task/tests/test_base.py @@ -13,6 +13,7 @@ from django.contrib.auth.models import User from django.test.utils import override_settings from capa.tests.response_xml_factory import OptionResponseXMLFactory +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -207,8 +208,10 @@ class InstructorTaskModuleTestCase(InstructorTaskCourseTestCase): problem_xml = factory.build_xml(**factory_args) location = InstructorTaskTestCase.problem_location(problem_url_name) item = self.module_store.get_item(location) - item.data = problem_xml - self.module_store.update_item(item, '**replace_user**') + with self.module_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, location.course_key): + item.data = problem_xml + self.module_store.update_item(item, self.user.id) + self.module_store.publish(location, self.user.id) def get_student_module(self, username, descriptor): """Get StudentModule object for test course, given the `username` and the problem's `descriptor`.""" diff --git a/lms/djangoapps/instructor_task/tests/test_integration.py b/lms/djangoapps/instructor_task/tests/test_integration.py index adfec85ee9..b75190e290 100644 --- a/lms/djangoapps/instructor_task/tests/test_integration.py +++ b/lms/djangoapps/instructor_task/tests/test_integration.py @@ -17,6 +17,7 @@ from django.core.urlresolvers import reverse from capa.tests.response_xml_factory import (CodeResponseXMLFactory, CustomResponseXMLFactory) from xmodule.modulestore.tests.factories import ItemFactory +from xmodule.modulestore import ModuleStoreEnum from courseware.model_data import StudentModule @@ -105,6 +106,9 @@ class TestRescoringTask(TestIntegrationTask): self.create_student('u4') self.logout() + # set up test user for performing test operations + self.setup_user() + def render_problem(self, username, problem_url_name): """ Use ajax interface to request html for a problem. @@ -294,7 +298,9 @@ class TestRescoringTask(TestIntegrationTask): InstructorTaskModuleTestCase.problem_location(problem_url_name) ) descriptor.data = problem_xml - self.module_store.update_item(descriptor, '**replace_user**') + with self.module_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, descriptor.location.course_key): + self.module_store.update_item(descriptor, self.user.id) + self.module_store.publish(descriptor.location, self.user.id) else: # Use "per-student" rerandomization so that check-problem can be called more than once. # Using "always" means we cannot check a problem twice, but we want to call once to get the diff --git a/lms/envs/test.py b/lms/envs/test.py index 6808be7b9a..1b70fe0746 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -109,7 +109,6 @@ STATICFILES_DIRS += [ if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir) ] -MODULESTORE_BRANCH = 'draft-preferred' update_module_store_settings( MODULESTORE, module_store_options={