diff --git a/common/lib/xmodule/xmodule/library_content_module.py b/common/lib/xmodule/xmodule/library_content_module.py index d8512bcdbe..49237c7881 100644 --- a/common/lib/xmodule/xmodule/library_content_module.py +++ b/common/lib/xmodule/xmodule/library_content_module.py @@ -361,6 +361,31 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe lib_tools.update_children(self, user_id, user_perms, update_db) return Response() + def _validate_library_version(self, validation, lib_tools, version, library_key): + latest_version = lib_tools.get_library_version(library_key) + if latest_version is not None: + if version is None or version != latest_version: + validation.set_summary( + StudioValidationMessage( + StudioValidationMessage.WARNING, + _(u'This component is out of date. The library has new content.'), + action_class='library-update-btn', # TODO: change this to action_runtime_event='...' once the unit page supports that feature. + action_label=_(u"↻ Update now") + ) + ) + return False + else: + validation.set_summary( + StudioValidationMessage( + StudioValidationMessage.ERROR, + _(u'Library is invalid, corrupt, or has been deleted.'), + action_class='edit-button', + action_label=_(u"Edit Library List") + ) + ) + return False + return True + def validate(self): """ Validates the state of this Library Content Module Instance. This @@ -381,30 +406,27 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ) return validation lib_tools = self.runtime.service(self, 'library_tools') + has_children_matching_filter = False for library_key, version in self.source_libraries: - latest_version = lib_tools.get_library_version(library_key) - if latest_version is not None: - if version is None or version != latest_version: - validation.set_summary( - StudioValidationMessage( - StudioValidationMessage.WARNING, - _(u'This component is out of date. The library has new content.'), - action_class='library-update-btn', # TODO: change this to action_runtime_event='...' once the unit page supports that feature. - action_label=_(u"↻ Update now") - ) - ) - break - else: - validation.set_summary( - StudioValidationMessage( - StudioValidationMessage.ERROR, - _(u'Library is invalid, corrupt, or has been deleted.'), - action_class='edit-button', - action_label=_(u"Edit Library List") - ) - ) + if not self._validate_library_version(validation, lib_tools, version, library_key): break + library = lib_tools.get_library(library_key) + children_matching_filter = lib_tools.get_filtered_children(library, self.capa_type) + # get_filtered_children returns generator, so we're basically checking if there are at least one child + # that satisfy filtering. Children are never equal to None, so None is returned only if generator was empty + has_children_matching_filter |= next(children_matching_filter, None) is not None + + if not has_children_matching_filter and validation.empty: + validation.set_summary( + StudioValidationMessage( + StudioValidationMessage.WARNING, + _(u'There are no content matching configured filters in the selected libraries.'), + action_class='edit-button', + action_label=_(u"Edit Library List") + ) + ) + return validation def editor_saved(self, user, old_metadata, old_content): diff --git a/common/lib/xmodule/xmodule/library_tools.py b/common/lib/xmodule/xmodule/library_tools.py index 3b902622c5..ad0d78a35b 100644 --- a/common/lib/xmodule/xmodule/library_tools.py +++ b/common/lib/xmodule/xmodule/library_tools.py @@ -18,7 +18,7 @@ class LibraryToolsService(object): def __init__(self, modulestore): self.store = modulestore - def _get_library(self, library_key): + def get_library(self, library_key): """ Given a library key like "library-v1:ProblemX+PR0B", return the 'library' XBlock with meta-information about the library. @@ -39,24 +39,39 @@ class LibraryToolsService(object): Get the version (an ObjectID) of the given library. Returns None if the library does not exist. """ - library = self._get_library(lib_key) + library = self.get_library(lib_key) if library: # We need to know the library's version so ensure it's set in library.location.library_key.version_guid assert library.location.library_key.version_guid is not None return library.location.library_key.version_guid return None - def _filter_child(self, dest_block, child_descriptor): + def _filter_child(self, capa_type, child_descriptor): """ Filters children by CAPA problem type, if configured """ - if dest_block.capa_type == ANY_CAPA_TYPE_VALUE: + if capa_type == ANY_CAPA_TYPE_VALUE: return True if not isinstance(child_descriptor, CapaDescriptor): return False - return dest_block.capa_type in child_descriptor.problem_types + return capa_type in child_descriptor.problem_types + + def get_filtered_children(self, from_block, capa_type=ANY_CAPA_TYPE_VALUE): + """ + Filters children of `from_block` that satisfy filter criteria + Returns generator containing (child_key, child) for all children matching filter criteria + """ + children = ( + (child_key, self.store.get_item(child_key, depth=9)) + for child_key in from_block.children + ) + return ( + (child_key, child) + for child_key, child in children + if self._filter_child(capa_type, child) + ) def update_children(self, dest_block, user_id, user_perms=None, update_db=True): """ @@ -89,7 +104,7 @@ class LibraryToolsService(object): # First, load and validate the source_libraries: libraries = [] for library_key, old_version in dest_block.source_libraries: # pylint: disable=unused-variable - library = self._get_library(library_key) + library = self.get_library(library_key) if library is None: raise ValueError("Required library not found.") if user_perms and not user_perms.can_read(library_key): @@ -109,11 +124,9 @@ class LibraryToolsService(object): Internal method to copy blocks from the library recursively """ new_children = [] - for child_key in from_block.children: - child = self.store.get_item(child_key, depth=9) - - if filter_problem_type and not self._filter_child(dest_block, child): - continue + target_capa_type = dest_block.capa_type if filter_problem_type else ANY_CAPA_TYPE_VALUE + filtered_children = self.get_filtered_children(from_block, target_capa_type) + for child_key, child in filtered_children: # We compute a block_id for each matching child block found in the library. # block_ids are unique within any branch, but are not unique per-course or globally. # We need our block_ids to be consistent when content in the library is updated, so