diff --git a/common/lib/xmodule/xmodule/library_content_module.py b/common/lib/xmodule/xmodule/library_content_module.py index d9e28e93fd..f89f147330 100644 --- a/common/lib/xmodule/xmodule/library_content_module.py +++ b/common/lib/xmodule/xmodule/library_content_module.py @@ -363,7 +363,7 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe js_module_name = "VerticalDescriptor" @XBlock.handler - def refresh_children(self, request, suffix): # pylint: disable=unused-argument + def refresh_children(self, request, suffix, update_db=True): # pylint: disable=unused-argument """ Refresh children: This method is to be used when any of the libraries that this block @@ -375,8 +375,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe This method will update this block's 'source_libraries' field to store the version number of the libraries used, so we easily determine if this block is up to date or not. + + If update_db is True (default), this will explicitly persist the changes + to the modulestore by calling update_item() """ - user_id = self.runtime.service(self, 'user').user_id + user_service = self.runtime.service(self, 'user') + user_id = user_service.user_id if user_service else None # May be None when creating bok choy test fixtures root_children = [] store = self.system.modulestore @@ -395,6 +399,8 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe new_libraries = [] for library_key, old_version in self.source_libraries: # pylint: disable=unused-variable library = _get_library(self.system.modulestore, library_key) # pylint: disable=protected-access + if library is None: + raise ValueError("Required library not found.") def copy_children_recursively(from_block): """ @@ -434,9 +440,21 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe new_libraries.append(LibraryVersionReference(library_key, library.location.library_key.version_guid)) self.source_libraries = new_libraries self.children = root_children # pylint: disable=attribute-defined-outside-init - self.system.modulestore.update_item(self, user_id) + if update_db: + self.system.modulestore.update_item(self, user_id) return Response() + def editor_saved(self, user, old_metadata, old_content): + """ + If source_libraries has been edited, refresh_children automatically. + """ + old_source_libraries = LibraryList().from_json(old_metadata.get('source_libraries', [])) + if set(old_source_libraries) != set(self.source_libraries): + try: + self.refresh_children(None, None, update_db=False) # update_db=False since update_item() is about to be called anyways + except ValueError: + pass # The validation area will display an error message, no need to do anything now. + def has_dynamic_children(self): """ Inform the runtime that our children vary per-user. diff --git a/common/test/acceptance/pages/studio/container.py b/common/test/acceptance/pages/studio/container.py index 58c4fe9235..a5ee42e9c4 100644 --- a/common/test/acceptance/pages/studio/container.py +++ b/common/test/acceptance/pages/studio/container.py @@ -312,6 +312,14 @@ class XBlockWrapper(PageObject): """ return self.q(css=self._bounded_selector('.xblock-student_view'))[0].text + @property + def author_content(self): + """ + Returns the text content of the xblock as displayed on the container page. + (For blocks which implement a distinct author_view). + """ + return self.q(css=self._bounded_selector('.xblock-author_view'))[0].text + @property def name(self): titles = self.q(css=self._bounded_selector(self.NAME_SELECTOR)).text diff --git a/common/test/acceptance/tests/lms/test_library.py b/common/test/acceptance/tests/lms/test_library.py index 78d699faa6..f83e6b94e9 100644 --- a/common/test/acceptance/tests/lms/test_library.py +++ b/common/test/acceptance/tests/lms/test_library.py @@ -92,7 +92,6 @@ class LibraryContentTest(UniqueCourseTest): modal = StudioLibraryContentXBlockEditModal(library_container_block.edit()) modal.count = count library_container_block.save_settings() - library_container_block.refresh_children() self._go_to_unit_page(change_login=False) unit_page.wait_for_page() unit_page.publish_action.click() diff --git a/common/test/acceptance/tests/studio/test_studio_library_container.py b/common/test/acceptance/tests/studio/test_studio_library_container.py index ba66bdd7b1..6e8fddeb8e 100644 --- a/common/test/acceptance/tests/studio/test_studio_library_container.py +++ b/common/test/acceptance/tests/studio/test_studio_library_container.py @@ -136,19 +136,29 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block + Then I update the library being used + Then I refresh the page Then I can see that library content block needs to be updated When I click on the update link Then I can see that the content no longer needs to be updated """ expected_text = "This component is out of date. The library has new content." - library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) + library_block = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) - self.assertTrue(library_container.has_validation_warning) - self.assertIn(expected_text, library_container.validation_warning_text) + self.assertFalse(library_block.has_validation_warning) + self.assertIn("3 matching components", library_block.author_content) - library_container.refresh_children() + self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc("html", "Html4")) + + self.unit_page.visit() # Reload the page + + self.assertTrue(library_block.has_validation_warning) + self.assertIn(expected_text, library_block.validation_warning_text) + + library_block.refresh_children() self.unit_page.wait_for_page() # Wait for the page to reload - library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) + library_block = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) - self.assertFalse(library_container.has_validation_message) + self.assertFalse(library_block.has_validation_message) + self.assertIn("4 matching components", library_block.author_content)