Ejemplo n.º 1
0
    def test_indexing_responses(self):
        """
        Test add_to_search_index response with real data
        """
        # Check results not indexed
        response = perform_search("unique",
                                  user=self.user,
                                  size=10,
                                  from_=0,
                                  course_id=unicode(self.course.id))
        self.assertEqual(response['results'], [])

        # Start manual reindex
        errors = CoursewareSearchIndexer.do_course_reindex(
            modulestore(), self.course.id)
        self.assertEqual(errors, None)

        self.html.display_name = "My expanded HTML"
        modulestore().update_item(self.html, ModuleStoreEnum.UserID.test)

        # Start manual reindex
        errors = CoursewareSearchIndexer.do_course_reindex(
            modulestore(), self.course.id)
        self.assertEqual(errors, None)

        # Check results indexed now
        response = perform_search("unique",
                                  user=self.user,
                                  size=10,
                                  from_=0,
                                  course_id=unicode(self.course.id))
        self.assertEqual(response['total'], 1)
Ejemplo n.º 2
0
    def publish(self, location, user_id, blacklist=None, **kwargs):
        """
        Publishes the subtree under location from the draft branch to the published branch
        Returns the newly published item.
        """
        super(DraftVersioningModuleStore, self).copy(
            user_id,
            # Directly using the replace function rather than the for_branch function
            # because for_branch obliterates the version_guid and will lead to missed version conflicts.
            # TODO Instead, the for_branch implementation should be fixed in the Opaque Keys library.
            location.course_key.replace(branch=ModuleStoreEnum.BranchName.draft
                                        ),
            # We clear out the version_guid here because the location here is from the draft branch, and that
            # won't have the same version guid
            location.course_key.replace(
                branch=ModuleStoreEnum.BranchName.published,
                version_guid=None),
            [location],
            blacklist=blacklist)

        # Now it's been published, add the object to the courseware search index so that it appears in search results
        CoursewareSearchIndexer.add_to_search_index(self, location)

        return self.get_item(
            location.for_branch(ModuleStoreEnum.BranchName.published),
            **kwargs)
Ejemplo n.º 3
0
    def test_indexing_responses(self):
        """
        Test add_to_search_index response with real data
        """
        # Check results not indexed
        response = perform_search(
            "unique",
            user=self.user,
            size=10,
            from_=0,
            course_id=unicode(self.course.id))
        self.assertEqual(response['results'], [])

        # Start manual reindex
        errors = CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
        self.assertEqual(errors, None)

        self.html.display_name = "My expanded HTML"
        modulestore().update_item(self.html, ModuleStoreEnum.UserID.test)

        # Start manual reindex
        errors = CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
        self.assertEqual(errors, None)

        # Check results indexed now
        response = perform_search(
            "unique",
            user=self.user,
            size=10,
            from_=0,
            course_id=unicode(self.course.id))
        self.assertEqual(response['total'], 1)
Ejemplo n.º 4
0
    def test_indexing_no_item(self, mock_get_course):
        """
        Test system logs an error if no item found.
        """
        # set mocked exception response
        err = ItemNotFoundError
        mock_get_course.return_value = err

        # Start manual reindex and check error in response
        with self.assertRaises(SearchIndexingError):
            CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
Ejemplo n.º 5
0
    def test_indexing_no_item(self, mock_get_course):
        """
        Test system logs an error if no item found.
        """
        # set mocked exception response
        err = ItemNotFoundError
        mock_get_course.return_value = err

        # Start manual reindex and check error in response
        with self.assertRaises(SearchIndexingError):
            CoursewareSearchIndexer.do_course_reindex(modulestore(),
                                                      self.course.id)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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
                    ]
                )

            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)
                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)
Ejemplo n.º 8
0
    def test_indexing_seq_error_responses(self, mock_index_dictionary):
        """
        Test add_to_search_index response with mocked error data for sequence
        """
        # Check results not indexed
        response = perform_search(
            "unique",
            user=self.user,
            size=10,
            from_=0,
            course_id=unicode(self.course.id))
        self.assertEqual(response['results'], [])

        # set mocked exception response
        err = Exception
        mock_index_dictionary.return_value = err

        # Start manual reindex and check error in response
        with self.assertRaises(SearchIndexingError):
            CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
Ejemplo n.º 9
0
    def test_indexing_seq_error_responses(self, mock_index_dictionary):
        """
        Test add_to_search_index response with mocked error data for sequence
        """
        # Check results not indexed
        response = perform_search("unique",
                                  user=self.user,
                                  size=10,
                                  from_=0,
                                  course_id=unicode(self.course.id))
        self.assertEqual(response['results'], [])

        # set mocked exception response
        err = Exception
        mock_index_dictionary.return_value = err

        # Start manual reindex and check error in response
        with self.assertRaises(SearchIndexingError):
            CoursewareSearchIndexer.do_course_reindex(modulestore(),
                                                      self.course.id)
Ejemplo n.º 10
0
    def publish(self, location, user_id, blacklist=None, **kwargs):
        """
        Publishes the subtree under location from the draft branch to the published branch
        Returns the newly published item.
        """
        super(DraftVersioningModuleStore, self).copy(
            user_id,
            # Directly using the replace function rather than the for_branch function
            # because for_branch obliterates the version_guid and will lead to missed version conflicts.
            # TODO Instead, the for_branch implementation should be fixed in the Opaque Keys library.
            location.course_key.replace(branch=ModuleStoreEnum.BranchName.draft),
            # We clear out the version_guid here because the location here is from the draft branch, and that
            # won't have the same version guid
            location.course_key.replace(branch=ModuleStoreEnum.BranchName.published, version_guid=None),
            [location],
            blacklist=blacklist
        )

        # Now it's been published, add the object to the courseware search index so that it appears in search results
        CoursewareSearchIndexer.add_to_search_index(self, location)

        return self.get_item(location.for_branch(ModuleStoreEnum.BranchName.published), **kwargs)
Ejemplo n.º 11
0
    def publish(self, location, user_id, **kwargs):
        """
        Publish the subtree rooted at location to the live course and remove the drafts.
        Such publishing may cause the deletion of previously published but subsequently deleted
        child trees. Overwrites any existing published xblocks from the subtree.

        Treats the publishing of non-draftable items as merely a subtree selection from
        which to descend.

        Raises:
            ItemNotFoundError: if any of the draft subtree nodes aren't found

        Returns:
            The newly published xblock
        """
        # NOTE: cannot easily use self._breadth_first b/c need to get pub'd and draft as pairs
        # (could do it by having 2 breadth first scans, the first to just get all published children
        # and the second to do the publishing on the drafts looking for the published in the cached
        # list of published ones.)
        to_be_deleted = []

        def _internal_depth_first(item_location, is_root):
            """
            Depth first publishing from the given location
            """
            try:
                # handle child does not exist w/o killing publish
                item = self.get_item(item_location)
            except ItemNotFoundError:
                log.warning('Cannot find: %s', item_location)
                return

            # publish the children first
            if item.has_children:
                for child_loc in item.children:
                    _internal_depth_first(child_loc, False)

            if item_location.category in DIRECT_ONLY_CATEGORIES or not getattr(item, 'is_draft', False):
                # ignore noop attempt to publish something that can't be or isn't currently draft
                return

            # try to find the originally PUBLISHED version, if it exists
            try:
                original_published = super(DraftModuleStore, self).get_item(item_location)
            except ItemNotFoundError:
                original_published = None

            # if the category of this item allows having children
            if item.has_children:
                if original_published is not None:
                    # see if previously published children were deleted. 2 reasons for children lists to differ:
                    #   Case 1: child deleted
                    #   Case 2: child moved
                    for orig_child in original_published.children:
                        if orig_child not in item.children:
                            published_parent = self.get_parent_location(orig_child)
                            if published_parent == item_location:
                                # Case 1: child was deleted in draft parent item
                                # So, delete published version of the child now that we're publishing the draft parent
                                self._delete_subtree(orig_child, [as_published])
                            else:
                                # Case 2: child was moved to a new draft parent item
                                # So, do not delete the child.  It will be published when the new parent is published.
                                pass

            # update the published (not draft) item (ignoring that item is "draft"). The published
            # may not exist; (if original_published is None); so, allow_not_found
            super(DraftModuleStore, self).update_item(
                item, user_id, isPublish=True, is_publish_root=is_root, allow_not_found=True
            )
            to_be_deleted.append(as_draft(item_location).to_deprecated_son())

        # verify input conditions
        self._verify_branch_setting(ModuleStoreEnum.Branch.draft_preferred)
        _verify_revision_is_published(location)

        _internal_depth_first(location, True)
        course_key = location.course_key
        bulk_record = self._get_bulk_ops_record(course_key)
        if len(to_be_deleted) > 0:
            bulk_record.dirty = True
            self.collection.remove({'_id': {'$in': to_be_deleted}})

        self._flag_publish_event(course_key)

        # Now it's been published, add the object to the courseware search index so that it appears in search results
        CoursewareSearchIndexer.do_publish_index(self, location)

        return self.get_item(as_published(location))
Ejemplo n.º 12
0
    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)

        # 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)
Ejemplo n.º 13
0
    def publish(self, location, user_id, **kwargs):
        """
        Publish the subtree rooted at location to the live course and remove the drafts.
        Such publishing may cause the deletion of previously published but subsequently deleted
        child trees. Overwrites any existing published xblocks from the subtree.

        Treats the publishing of non-draftable items as merely a subtree selection from
        which to descend.

        Raises:
            ItemNotFoundError: if any of the draft subtree nodes aren't found

        Returns:
            The newly published xblock
        """
        # NOTE: cannot easily use self._breadth_first b/c need to get pub'd and draft as pairs
        # (could do it by having 2 breadth first scans, the first to just get all published children
        # and the second to do the publishing on the drafts looking for the published in the cached
        # list of published ones.)
        to_be_deleted = []

        def _internal_depth_first(item_location, is_root):
            """
            Depth first publishing from the given location
            """
            try:
                # handle child does not exist w/o killing publish
                item = self.get_item(item_location)
            except ItemNotFoundError:
                log.warning('Cannot find: %s', item_location)
                return

            # publish the children first
            if item.has_children:
                for child_loc in item.children:
                    _internal_depth_first(child_loc, False)

            if item_location.category in DIRECT_ONLY_CATEGORIES or not getattr(
                    item, 'is_draft', False):
                # ignore noop attempt to publish something that can't be or isn't currently draft
                return

            # try to find the originally PUBLISHED version, if it exists
            try:
                original_published = super(DraftModuleStore,
                                           self).get_item(item_location)
            except ItemNotFoundError:
                original_published = None

            # if the category of this item allows having children
            if item.has_children:
                if original_published is not None:
                    # see if previously published children were deleted. 2 reasons for children lists to differ:
                    #   Case 1: child deleted
                    #   Case 2: child moved
                    for orig_child in original_published.children:
                        if orig_child not in item.children:
                            published_parent = self.get_parent_location(
                                orig_child)
                            if published_parent == item_location:
                                # Case 1: child was deleted in draft parent item
                                # So, delete published version of the child now that we're publishing the draft parent
                                self._delete_subtree(orig_child,
                                                     [as_published])
                            else:
                                # Case 2: child was moved to a new draft parent item
                                # So, do not delete the child.  It will be published when the new parent is published.
                                pass

            # update the published (not draft) item (ignoring that item is "draft"). The published
            # may not exist; (if original_published is None); so, allow_not_found
            super(DraftModuleStore, self).update_item(item,
                                                      user_id,
                                                      isPublish=True,
                                                      is_publish_root=is_root,
                                                      allow_not_found=True)
            to_be_deleted.append(as_draft(item_location).to_deprecated_son())

        # verify input conditions
        self._verify_branch_setting(ModuleStoreEnum.Branch.draft_preferred)
        _verify_revision_is_published(location)

        _internal_depth_first(location, True)
        course_key = location.course_key
        bulk_record = self._get_bulk_ops_record(course_key)
        if len(to_be_deleted) > 0:
            bulk_record.dirty = True
            self.collection.remove({'_id': {'$in': to_be_deleted}})

        self._flag_publish_event(course_key)

        # Now it's been published, add the object to the courseware search index so that it appears in search results
        CoursewareSearchIndexer.do_publish_index(self, location)

        return self.get_item(as_published(location))
Ejemplo n.º 14
0
    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)

        # 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)