def delete_item(self, location, user_id, revision=None, **kwargs): """ Delete the given item from persistence. kwargs allow modulestore specific parameters. Args: location: UsageKey of the item to be deleted user_id: id of the user deleting the item revision: None - deletes the item and its subtree, and updates the parents per description above ModuleStoreEnum.RevisionOption.published_only - removes only Published versions ModuleStoreEnum.RevisionOption.all - removes both Draft and Published parents currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ with self.bulk_operations(location.course_key): if revision == ModuleStoreEnum.RevisionOption.published_only: branches_to_delete = [ModuleStoreEnum.BranchName.published] elif revision == ModuleStoreEnum.RevisionOption.all: branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] elif revision is None: branches_to_delete = [ModuleStoreEnum.BranchName.draft] else: raise UnsupportedRevisionError( [ None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all ] ) for branch in branches_to_delete: branched_location = location.for_branch(branch) parent_loc = self.get_parent_location(branched_location) SplitMongoModuleStore.delete_item(self, branched_location, user_id) self._auto_publish_no_children(parent_loc, parent_loc.category, user_id, **kwargs)
def _map_revision_to_branch(self, key, revision=None): """ Maps RevisionOptions to BranchNames, inserting them into the key """ if isinstance(key, (LibraryLocator, LibraryUsageLocator)): # Libraries don't yet have draft/publish support: draft_branch = ModuleStoreEnum.BranchName.library published_branch = ModuleStoreEnum.BranchName.library else: draft_branch = ModuleStoreEnum.BranchName.draft published_branch = ModuleStoreEnum.BranchName.published if revision == ModuleStoreEnum.RevisionOption.published_only: return key.for_branch(published_branch) elif revision == ModuleStoreEnum.RevisionOption.draft_only: return key.for_branch(draft_branch) elif revision is None: if key.branch is not None: return key elif self.get_branch_setting( key) == ModuleStoreEnum.Branch.draft_preferred: return key.for_branch(draft_branch) else: return key.for_branch(published_branch) else: raise UnsupportedRevisionError()
def has_item(self, usage_key, revision=None): """ Returns True if location exists in this ModuleStore. Args: revision: ModuleStoreEnum.RevisionOption.published_only - checks for the published item only ModuleStoreEnum.RevisionOption.draft_only - checks for the draft item only None - uses the branch setting, as follows: if branch setting is ModuleStoreEnum.Branch.published_only, checks for the published item only if branch setting is ModuleStoreEnum.Branch.draft_preferred, checks whether draft or published item exists """ def has_published(): return super(DraftModuleStore, self).has_item(usage_key) def has_draft(): return super(DraftModuleStore, self).has_item(as_draft(usage_key)) if revision == ModuleStoreEnum.RevisionOption.draft_only: return has_draft() elif (revision == ModuleStoreEnum.RevisionOption.published_only or self.get_branch_setting() == ModuleStoreEnum.Branch.published_only): return has_published() elif revision is None: key = usage_key.to_deprecated_son(prefix='_id.') del key['_id.revision'] return self.collection.find(key).count() > 0 else: raise UnsupportedRevisionError()
def has_item(self, usage_key, revision=None): # lint-amnesty, pylint: disable=arguments-differ """ Returns True if location exists in this ModuleStore. Args: revision: ModuleStoreEnum.RevisionOption.published_only - checks for the published item only ModuleStoreEnum.RevisionOption.draft_only - checks for the draft item only None - uses the branch setting, as follows: if branch setting is ModuleStoreEnum.Branch.published_only, checks for the published item only if branch setting is ModuleStoreEnum.Branch.draft_preferred, checks whether draft or published item exists # lint-amnesty, pylint: disable=line-too-long """ def has_published(): return super(DraftModuleStore, self).has_item(usage_key) # lint-amnesty, pylint: disable=super-with-arguments def has_draft(): return super(DraftModuleStore, self).has_item(as_draft(usage_key)) # lint-amnesty, pylint: disable=super-with-arguments if revision == ModuleStoreEnum.RevisionOption.draft_only: return has_draft() elif (revision == ModuleStoreEnum.RevisionOption.published_only or self.get_branch_setting() == ModuleStoreEnum.Branch.published_only): return has_published() elif revision is None: key = usage_key.to_deprecated_son(prefix='_id.') del key['_id.revision'] return self.collection.count_documents(key) > 0 else: raise UnsupportedRevisionError()
def delete_item(self, location, user_id, revision=None, skip_auto_publish=False, **kwargs): """ Delete the given item from persistence. kwargs allow modulestore specific parameters. Args: location: UsageKey of the item to be deleted user_id: id of the user deleting the item revision: None - deletes the item and its subtree, and updates the parents per description above ModuleStoreEnum.RevisionOption.published_only - removes only Published versions ModuleStoreEnum.RevisionOption.all - removes both Draft and Published parents currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ with self.bulk_operations(location.course_key): if isinstance(location, LibraryUsageLocator): branches_to_delete = [ModuleStoreEnum.BranchName.library] # Libraries don't yet have draft/publish support elif revision == ModuleStoreEnum.RevisionOption.published_only: branches_to_delete = [ModuleStoreEnum.BranchName.published] elif revision == ModuleStoreEnum.RevisionOption.all: branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] elif revision is None: branches_to_delete = [ModuleStoreEnum.BranchName.draft] draft_location = location.for_branch(ModuleStoreEnum.BranchName.draft) try: item = self.get_item(draft_location) if getattr(item, 'has_children', False): # If item have has_published_version then delete published children also. if self.has_published_version(item): branches_to_delete.insert(0, ModuleStoreEnum.BranchName.published) except ItemNotFoundError: # Raises ValueError as in function description raise ValueError("Cannot delete a block that does not exist") else: raise UnsupportedRevisionError( [ None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all ] ) self._flag_publish_event(location.course_key) for branch in branches_to_delete: branched_location = location.for_branch(branch) parent_loc = self.get_parent_location(branched_location) SplitMongoModuleStore.delete_item(self, branched_location, user_id) # publish parent w/o child if deleted element is direct only (not based on type of parent) # publish vertical to behave more like the old mongo/draft modulestore - TNL-2593 if ( branch == ModuleStoreEnum.BranchName.draft and branched_location.block_type in (DIRECT_ONLY_CATEGORIES + ['vertical']) and parent_loc and not skip_auto_publish ): # will publish if its not an orphan self.publish(parent_loc.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs)
def _map_revision_to_branch(self, key, revision=None): """ Maps RevisionOptions to BranchNames, inserting them into the key """ if revision == ModuleStoreEnum.RevisionOption.published_only: return key.for_branch(ModuleStoreEnum.BranchName.published) elif revision == ModuleStoreEnum.RevisionOption.draft_only: return key.for_branch(ModuleStoreEnum.BranchName.draft) elif revision is None: return key else: raise UnsupportedRevisionError()
def get_items(self, course_key, revision=None, **kwargs): """ Performance Note: This is generally a costly operation, but useful for wildcard searches. Returns: list of XModuleDescriptor instances for the matching items within the course with the given course_key NOTE: don't use this to look for courses as the course_key is required. Use get_courses instead. Args: course_key (CourseKey): the course identifier revision: ModuleStoreEnum.RevisionOption.published_only - returns only Published items ModuleStoreEnum.RevisionOption.draft_only - returns only Draft items None - uses the branch setting, as follows: if the branch setting is ModuleStoreEnum.Branch.published_only, returns only Published items if the branch setting is ModuleStoreEnum.Branch.draft_preferred, returns either Draft or Published, preferring Draft items. """ def base_get_items(key_revision): return super(DraftModuleStore, self).get_items(course_key, key_revision=key_revision, **kwargs) def draft_items(): return [ wrap_draft(item) for item in base_get_items(MongoRevisionKey.draft) ] def published_items(draft_items): # filters out items that are not already in draft_items draft_items_locations = {item.location for item in draft_items} return [ item for item in base_get_items(MongoRevisionKey.published) if item.location not in draft_items_locations ] if revision == ModuleStoreEnum.RevisionOption.draft_only: return draft_items() elif revision == ModuleStoreEnum.RevisionOption.published_only \ or self.get_branch_setting() == ModuleStoreEnum.Branch.published_only: return published_items([]) elif revision is None: draft_items = draft_items() return draft_items + published_items(draft_items) else: raise UnsupportedRevisionError()
def delete_item(self, location, user_id, revision=None, **kwargs): """ Delete the given item from persistence. kwargs allow modulestore specific parameters. Args: location: UsageKey of the item to be deleted user_id: id of the user deleting the item revision: None - deletes the item and its subtree, and updates the parents per description above ModuleStoreEnum.RevisionOption.published_only - removes only Published versions ModuleStoreEnum.RevisionOption.all - removes both Draft and Published parents currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ with self.bulk_operations(location.course_key): if isinstance(location, LibraryUsageLocator): branches_to_delete = [ ModuleStoreEnum.BranchName.library ] # Libraries don't yet have draft/publish support elif revision == ModuleStoreEnum.RevisionOption.published_only: branches_to_delete = [ModuleStoreEnum.BranchName.published] elif revision == ModuleStoreEnum.RevisionOption.all: branches_to_delete = [ ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft ] elif revision is None: branches_to_delete = [ModuleStoreEnum.BranchName.draft] else: raise UnsupportedRevisionError([ None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all ]) for branch in branches_to_delete: branched_location = location.for_branch(branch) parent_loc = self.get_parent_location(branched_location) SplitMongoModuleStore.delete_item(self, branched_location, user_id) # publish parent w/o child if deleted element is direct only (not based on type of parent) if branch == ModuleStoreEnum.BranchName.draft and branched_location.block_type in DIRECT_ONLY_CATEGORIES: self.publish(parent_loc.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs) # Remove this location from the courseware search index so that searches # will refrain from showing it as a result CoursewareSearchIndexer.add_to_search_index(self, location, delete=True)
def delete_item(self, location, user_id, revision=None, skip_auto_publish=False, **kwargs): # lint-amnesty, pylint: disable=arguments-differ """ Delete the given item from persistence. kwargs allow modulestore specific parameters. Args: location: UsageKey of the item to be deleted user_id: id of the user deleting the item revision: None - deletes the item and its subtree, and updates the parents per description above ModuleStoreEnum.RevisionOption.published_only - removes only Published versions ModuleStoreEnum.RevisionOption.all - removes both Draft and Published parents currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ allowed_revisions = [ None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all ] if revision not in allowed_revisions: raise UnsupportedRevisionError(allowed_revisions) autopublish_parent = False with self.bulk_operations(location.course_key): if isinstance(location, LibraryUsageLocator): branches_to_delete = [ModuleStoreEnum.BranchName.library] # Libraries don't yet have draft/publish support # lint-amnesty, pylint: disable=line-too-long elif location.block_type in DIRECT_ONLY_CATEGORIES: branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] elif revision == ModuleStoreEnum.RevisionOption.all: branches_to_delete = [ModuleStoreEnum.BranchName.published, ModuleStoreEnum.BranchName.draft] else: if revision == ModuleStoreEnum.RevisionOption.published_only: branches_to_delete = [ModuleStoreEnum.BranchName.published] elif revision is None: branches_to_delete = [ModuleStoreEnum.BranchName.draft] parent_loc = self.get_parent_location(location.for_branch(ModuleStoreEnum.BranchName.draft)) autopublish_parent = ( not skip_auto_publish and parent_loc is not None and parent_loc.block_type in DIRECT_ONLY_CATEGORIES ) self._flag_publish_event(location.course_key) for branch in branches_to_delete: branched_location = location.for_branch(branch) super().delete_item(branched_location, user_id) if autopublish_parent: self.publish(parent_loc.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs)
def delete_item(self, location, user_id, revision=None, **kwargs): """ Delete an item from this modulestore. The method determines which revisions to delete. It disconnects and deletes the subtree. In general, it assumes deletes only occur on drafts except for direct_only. The only exceptions are internal calls like deleting orphans (during publishing as well as from delete_orphan view). To indicate that all versions should be deleted, pass the keyword revision=ModuleStoreEnum.RevisionOption.all. * Deleting a DIRECT_ONLY_CATEGORIES block, deletes both draft and published children and removes from parent. * Deleting a specific version of block whose parent is of DIRECT_ONLY_CATEGORIES, only removes it from parent if the other version of the block does not exist. Deletes only children of same version. * Other deletions remove from parent of same version and subtree of same version Args: location: UsageKey of the item to be deleted user_id: id of the user deleting the item revision: None - deletes the item and its subtree, and updates the parents per description above ModuleStoreEnum.RevisionOption.published_only - removes only Published versions ModuleStoreEnum.RevisionOption.all - removes both Draft and Published parents currently only provided by contentstore.views.item.orphan_handler Otherwise, raises a ValueError. """ self._verify_branch_setting(ModuleStoreEnum.Branch.draft_preferred) _verify_revision_is_published(location) is_item_direct_only = location.category in DIRECT_ONLY_CATEGORIES if is_item_direct_only or revision == ModuleStoreEnum.RevisionOption.published_only: parent_revision = MongoRevisionKey.published elif revision == ModuleStoreEnum.RevisionOption.all: parent_revision = ModuleStoreEnum.RevisionOption.all else: parent_revision = MongoRevisionKey.draft # remove subtree from its parent parent_locations = self._get_raw_parent_locations( location, key_revision=parent_revision) # if no parents, then we're trying to delete something which we should convert to draft if not parent_locations: # find the published parent, convert it to draft, then manipulate the draft parent_locations = self._get_raw_parent_locations( location, key_revision=MongoRevisionKey.published) # parent_locations will still be empty if the object was an orphan if parent_locations: draft_parent = self.convert_to_draft(parent_locations[0], user_id) parent_locations = [draft_parent.location] # there could be 2 parents if # Case 1: the draft item moved from one parent to another # Case 2: revision==ModuleStoreEnum.RevisionOption.all and the single # parent has 2 versions: draft and published for parent_location in parent_locations: # don't remove from direct_only parent if other versions of this still exists (this code # assumes that there's only one parent_location in this case) if not is_item_direct_only and parent_location.category in DIRECT_ONLY_CATEGORIES: # see if other version of to-be-deleted root exists query = location.to_deprecated_son(prefix='_id.') del query['_id.revision'] if self.collection.find(query).count() > 1: continue parent_block = super(DraftModuleStore, self).get_item(parent_location) parent_block.children.remove(location) parent_block.location = parent_location # ensure the location is with the correct revision self.update_item(parent_block, user_id, child_update=True) self._flag_publish_event(location.course_key) if is_item_direct_only or revision == ModuleStoreEnum.RevisionOption.all: as_functions = [as_draft, as_published] elif revision == ModuleStoreEnum.RevisionOption.published_only: as_functions = [as_published] elif revision is None: as_functions = [as_draft] else: raise UnsupportedRevisionError([ None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all ]) self._delete_subtree(location, as_functions)
def get_item(self, usage_key, depth=0, revision=None, using_descriptor_system=None, **kwargs): """ Returns an XModuleDescriptor instance for the item at usage_key. Args: usage_key: A :class:`.UsageKey` instance depth (int): An argument that some module stores may use to prefetch descendants of the queried modules for more efficient results later in the request. The depth is counted in the number of calls to get_children() to cache. None indicates to cache all descendants. revision: ModuleStoreEnum.RevisionOption.published_only - returns only the published item. ModuleStoreEnum.RevisionOption.draft_only - returns only the draft item. None - uses the branch setting as follows: if branch setting is ModuleStoreEnum.Branch.published_only, returns only the published item. if branch setting is ModuleStoreEnum.Branch.draft_preferred, returns either draft or published item, preferring draft. Note: If the item is in DIRECT_ONLY_CATEGORIES, then returns only the PUBLISHED version regardless of the revision. using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem to add data to, and to load the XBlocks from. Raises: xmodule.modulestore.exceptions.InsufficientSpecificationError if any segment of the usage_key is None except revision xmodule.modulestore.exceptions.ItemNotFoundError if no object is found at that usage_key """ def get_published(): return wrap_draft( super(DraftModuleStore, self).get_item( usage_key, depth=depth, using_descriptor_system=using_descriptor_system, for_parent=kwargs.get('for_parent'), )) def get_draft(): return wrap_draft( super(DraftModuleStore, self).get_item( as_draft(usage_key), depth=depth, using_descriptor_system=using_descriptor_system, for_parent=kwargs.get('for_parent'))) # return the published version if ModuleStoreEnum.RevisionOption.published_only is requested if revision == ModuleStoreEnum.RevisionOption.published_only: return get_published() # if the item is direct-only, there can only be a published version elif usage_key.category in DIRECT_ONLY_CATEGORIES: return get_published() # return the draft version (without any fallback to PUBLISHED) if DRAFT-ONLY is requested elif revision == ModuleStoreEnum.RevisionOption.draft_only: return get_draft() elif self.get_branch_setting( ) == ModuleStoreEnum.Branch.published_only: return get_published() elif revision is None: # could use a single query wildcarding revision and sorting by revision. would need to # use prefix form of to_deprecated_son try: # first check for a draft version return get_draft() except ItemNotFoundError: # otherwise, fall back to the published version return get_published() else: raise UnsupportedRevisionError()