From 8855b12b76eed1a9a6a7da8128d72e48258e6342 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Sun, 6 Jul 2014 17:35:26 -0400 Subject: [PATCH] Update tests to allow LMS tests to use published branch setting. Conflicts: cms/djangoapps/contentstore/views/tests/test_container_page.py common/djangoapps/external_auth/tests/test_shib.py common/djangoapps/student/tests/test_login.py common/lib/xmodule/xmodule/modulestore/tests/django_utils.py common/lib/xmodule/xmodule/modulestore/tests/factories.py common/lib/xmodule/xmodule/modulestore/xml_importer.py lms/djangoapps/branding/tests.py lms/djangoapps/courseware/tests/test_submitting_problems.py lms/djangoapps/courseware/tests/test_video_handlers.py lms/djangoapps/instructor_task/tests/test_base.py lms/djangoapps/instructor_task/tests/test_integration.py --- cms/djangoapps/contentstore/tests/utils.py | 2 +- .../views/tests/test_container_page.py | 43 ++- common/djangoapps/contentserver/tests/test.py | 2 +- .../external_auth/tests/test_shib.py | 46 ++- common/djangoapps/student/tests/test_login.py | 17 +- .../xmodule/modulestore/tests/django_utils.py | 22 +- .../xmodule/modulestore/tests/factories.py | 98 +++--- .../xmodule/modulestore/xml_importer.py | 297 +++++++++--------- lms/djangoapps/branding/tests.py | 9 +- .../courseware/features/annotatable.py | 6 +- lms/djangoapps/courseware/features/common.py | 4 - .../courseware/features/conditional.py | 5 +- lms/djangoapps/courseware/features/gst.py | 2 +- .../courseware/tests/test_lti_integration.py | 6 +- .../courseware/tests/test_video_handlers.py | 4 +- .../instructor_task/tests/test_base.py | 7 +- .../instructor_task/tests/test_integration.py | 5 +- lms/envs/test.py | 1 - 18 files changed, 314 insertions(+), 262 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index a7a8932221..e95bfd92a3 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -89,7 +89,7 @@ class CourseTestCase(ModuleStoreTestCase): """ Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing. """ - nonstaff, password = super(CourseTestCase, self).create_non_staff_authed_user_client() + nonstaff, password = self.create_non_staff_user() client = Client() if authenticate: diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py index d1d700f6d2..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,8 +124,7 @@ 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') + 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/common/djangoapps/contentserver/tests/test.py b/common/djangoapps/contentserver/tests/test.py index bb4aae815e..5e1f3bf084 100644 --- a/common/djangoapps/contentserver/tests/test.py +++ b/common/djangoapps/contentserver/tests/test.py @@ -35,7 +35,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): """ self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp() self.staff_usr = self.user - self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_authed_user_client() + self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user() self.client = Client() self.contentstore = contentstore() diff --git a/common/djangoapps/external_auth/tests/test_shib.py b/common/djangoapps/external_auth/tests/test_shib.py index 79658b4b18..bd050384aa 100644 --- a/common/djangoapps/external_auth/tests/test_shib.py +++ b/common/djangoapps/external_auth/tests/test_shib.py @@ -18,6 +18,7 @@ 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.django import modulestore +from xmodule.modulestore import ModuleStoreEnum from opaque_keys.edx.locations import SlashSeparatedCourseKey from external_auth.models import ExternalAuthMap @@ -80,6 +81,7 @@ class ShibSPTest(ModuleStoreTestCase): def setUp(self): 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): @@ -376,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, self.user.id) + + # 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' + @@ -449,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, self.user.id) + 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, self.user.id) + 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() @@ -519,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, self.user.id) + 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_login.py b/common/djangoapps/student/tests/test_login.py index 0cb1fb7847..ad715a8dff 100644 --- a/common/djangoapps/student/tests/test_login.py +++ b/common/djangoapps/student/tests/test_login.py @@ -346,10 +346,19 @@ class ExternalAuthShibTest(ModuleStoreTestCase): """ def setUp(self): super(ExternalAuthShibTest, self).setUp() - 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, self.user.id) + 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/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 77453b3f7c..745784c85d 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -129,6 +129,13 @@ class ModuleStoreTestCase(TestCase): your `setUp()` method. """ def setUp(self, **kwargs): + """ + Creates a test User if `create_user` is True. + Returns the password for the test User. + + Args: + create_user - specifies whether or not to create a test User. Default is True. + """ super(ModuleStoreTestCase, self).setUp() self.store = modulestore() @@ -151,20 +158,21 @@ class ModuleStoreTestCase(TestCase): return password - def create_non_staff_authed_user_client(self): + def create_non_staff_user(self): """ - Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing. + Creates a non-staff test user. + Returns the non-staff test user and its password. """ uname = 'teststudent' password = 'foo' - nonstaff = User.objects.create_user(uname, 'test+student@edx.org', password) + 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.is_active = True - nonstaff.is_staff = False - nonstaff.save() - return nonstaff, password + nonstaff_user.is_active = True + nonstaff_user.is_staff = False + nonstaff_user.save() + return nonstaff_user, password def update_course(self, course, user_id): """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 1a4fc522d2..08ea97d744 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -56,18 +56,19 @@ class CourseFactory(XModuleFactory): 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, user_id) - 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): @@ -129,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 """ @@ -145,6 +148,7 @@ class ItemFactory(XModuleFactory): metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) + publish_item = kwargs.pop('publish_item', True) assert isinstance(location, Location) assert location != parent_location @@ -154,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, user_id) + 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, user_id) + 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, user_id) + 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/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 857f61d238..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, 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) - ) - 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/lms/djangoapps/branding/tests.py b/lms/djangoapps/branding/tests.py index aceea7a318..5faab59a7a 100644 --- a/lms/djangoapps/branding/tests.py +++ b/lms/djangoapps/branding/tests.py @@ -30,10 +30,11 @@ class AnonymousIndexPageTest(ModuleStoreTestCase): def setUp(self): 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, self.user.id) + 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/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_video_handlers.py b/lms/djangoapps/courseware/tests/test_video_handlers.py index e54bc83d8e..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, self.user.id) + 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/instructor_task/tests/test_base.py b/lms/djangoapps/instructor_task/tests/test_base.py index 31fb6b8107..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, self.user.id) + 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 5bc6e1ff91..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 @@ -297,7 +298,9 @@ class TestRescoringTask(TestIntegrationTask): InstructorTaskModuleTestCase.problem_location(problem_url_name) ) descriptor.data = problem_xml - self.module_store.update_item(descriptor, self.user.id) + 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={