コード例 #1
0
ファイル: content.py プロジェクト: qyqx/tracim
class ItemLocationController(TIMWorkspaceContentRestController,
                             BaseController):
    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def get_one(self, item_id):
        item_id = int(item_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        item = ContentApi(user).get_one(item_id, ContentType.Any, workspace)
        raise NotImplementedError
        return item

    @tg.require(current_user_is_content_manager())
    @tg.expose('tracim.templates.folder.move')
    def edit(self, item_id):
        """
        Show the edit form (do not really edit the data)

        :param item_id:
        :return:
        """
        current_user_content = \
            Context(CTX.CURRENT_USER).toDict(tmpl_context.current_user)
        fake_api = \
            Context(CTX.FOLDER) \
            .toDict(DictLikeClass(current_user=current_user_content))

        item_id = int(item_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        content_api = ContentApi(user)
        item = content_api.get_one(item_id, ContentType.Any, workspace)

        dictified_item = Context(CTX.DEFAULT).toDict(item, 'item')
        return DictLikeClass(result=dictified_item, fake_api=fake_api)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put(self, item_id, folder_id='0'):
        """
        :param item_id:
        :param folder_id: id of the folder, in a style like
                          'workspace_14__content_1586'
        :return:
        """
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace
        item_id = int(item_id)
        new_workspace, new_parent = convert_id_into_instances(folder_id)

        if new_workspace != workspace:
            # check that user is at least
            # - content manager in current workspace
            # - content manager in new workspace
            user = tmpl_context.current_user

            if user.get_role(workspace) < UserRoleInWorkspace.CONTENT_MANAGER:
                tg.flash(_('You are not allowed '
                           'to move this folder'), CST.STATUS_ERROR)
                tg.redirect(self.parent_controller.url(item_id))

            if user.get_role(
                    new_workspace) < UserRoleInWorkspace.CONTENT_MANAGER:
                tg.flash(
                    _('You are not allowed to move '
                      'this folder to this workspace'), CST.STATUS_ERROR)
                tg.redirect(self.parent_controller.url(item_id))

            api = ContentApi(tmpl_context.current_user)
            item = api.get_one(item_id, ContentType.Any, workspace)

            with new_revision(item):
                api.move_recursively(item, new_parent, new_workspace)

            next_url = tg.url('/workspaces/{}/folders/{}'.format(
                new_workspace.workspace_id, item_id))
            if new_parent:
                tg.flash(
                    _('Item moved to {} (workspace {})').format(
                        new_parent.label, new_workspace.label), CST.STATUS_OK)
            else:
                tg.flash(
                    _('Item moved to workspace {}').format(
                        new_workspace.label))

            tg.redirect(next_url)

        else:
            # Default move inside same workspace
            api = ContentApi(tmpl_context.current_user)
            item = api.get_one(item_id, ContentType.Any, workspace)
            with new_revision(item):
                api.move(item, new_parent)
            next_url = self.parent_controller.url(item_id)
            if new_parent:
                tg.flash(
                    _('Item moved to {}').format(new_parent.label),
                    CST.STATUS_OK)
            else:
                tg.flash(_('Item moved to workspace root'))

            tg.redirect(next_url)
コード例 #2
0
ファイル: content.py プロジェクト: qyqx/tracim
class UserWorkspaceFolderRestController(TIMRestControllerWithBreadcrumb):

    TEMPLATE_NEW = 'mako:tracim.templates.folder.new'

    location = ItemLocationController()

    files = UserWorkspaceFolderFileRestController()
    pages = UserWorkspaceFolderPageRestController()
    threads = UserWorkspaceFolderThreadRestController()

    def _before(self, *args, **kw):
        TIMRestPathContextSetup.current_user()
        try:
            TIMRestPathContextSetup.current_workspace()
        except NoResultFound:
            abort(404)

    @tg.require(current_user_is_content_manager())
    @tg.expose('tracim.templates.folder.edit')
    def edit(self, folder_id):
        """
        Show the edit form (do not really edit the data)

        :param item_id:
        :return:
        """

        folder_id = int(folder_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        content_api = ContentApi(user)
        folder = content_api.get_one(folder_id, ContentType.Folder, workspace)

        dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
        return DictLikeClass(result=dictified_folder)

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.folder.getone')
    def get_one(self, folder_id, **kwargs):
        """
        :param folder_id: Displayed folder id
        :param kwargs:
          * show_deleted: bool: Display deleted contents or hide them if False
          * show_archived: bool: Display archived contents or hide them
            if False
        """
        show_deleted = str_as_bool(kwargs.get('show_deleted', ''))
        show_archived = str_as_bool(kwargs.get('show_archived', ''))
        folder_id = int(folder_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        current_user_content = Context(CTX.CURRENT_USER,
                                       current_user=user).toDict(user)
        current_user_content.roles.sort(key=lambda role: role.workspace.name)

        content_api = ContentApi(
            user,
            show_deleted=show_deleted,
            show_archived=show_archived,
        )
        with content_api.show(show_deleted=True, show_archived=True):
            folder = content_api.get_one(
                folder_id,
                ContentType.Folder,
                workspace,
            )

        fake_api_breadcrumb = self.get_breadcrumb(folder_id)
        fake_api_subfolders = self.get_all_fake(workspace,
                                                folder.content_id).result
        fake_api_pages = self.pages.get_all_fake(workspace, folder).result
        fake_api_files = self.files.get_all_fake(workspace, folder).result
        fake_api_threads = self.threads.get_all_fake(workspace, folder).result

        fake_api_content = DictLikeClass(
            current_user=current_user_content,
            breadcrumb=fake_api_breadcrumb,
            current_folder_subfolders=fake_api_subfolders,
            current_folder_pages=fake_api_pages,
            current_folder_files=fake_api_files,
            current_folder_threads=fake_api_threads,
        )

        fake_api = Context(CTX.FOLDER).toDict(fake_api_content)

        sub_items = content_api.get_children(
            parent_id=folder.content_id,
            content_types=[
                ContentType.Folder,
                ContentType.File,
                ContentType.Page,
                ContentType.Thread,
            ],
        )
        fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(sub_items)

        fake_api.content_types = Context(CTX.DEFAULT).toDict(
            content_api.get_all_types())

        dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
        return DictLikeClass(
            result=dictified_folder,
            fake_api=fake_api,
            show_deleted=show_deleted,
            show_archived=show_archived,
        )

    def get_all_fake(self, context_workspace: Workspace, parent_id=None):
        """
        fake methods are used in other controllers in order to simulate a
        client/server api.  the "client" controller method will include the
        result into its own fake_api object which will be available in the
        templates

        :param context_workspace: the workspace which would be taken from
                                  tmpl_context if we were in the normal
                                  behavior
        :return:
        """
        workspace = context_workspace
        content_api = ContentApi(tmpl_context.current_user)
        with content_api.show(show_deleted=True, show_archived=True):
            parent_folder = content_api.get_one(parent_id, ContentType.Folder)
        folders = content_api.get_child_folders(parent_folder, workspace)

        folders = Context(CTX.FOLDERS).toDict(folders)
        return DictLikeClass(result=folders)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def post(self,
             label,
             parent_id=None,
             can_contain_folders=False,
             can_contain_threads=False,
             can_contain_files=False,
             can_contain_pages=False):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        redirect_url_tmpl = '/workspaces/{}/folders/{}'
        redirect_url = ''

        try:
            parent = None
            if parent_id:
                parent = api.get_one(int(parent_id), ContentType.Folder,
                                     workspace)

            with DBSession.no_autoflush:
                folder = api.create(ContentType.Folder, workspace, parent,
                                    label)

                subcontent = dict(
                    folder=True if can_contain_folders == 'on' else False,
                    thread=True if can_contain_threads == 'on' else False,
                    file=True if can_contain_files == 'on' else False,
                    page=True if can_contain_pages == 'on' else False)
                api.set_allowed_content(folder, subcontent)

                if not self._path_validation.validate_new_content(folder):
                    return render_invalid_integrity_chosen_path(
                        folder.get_label(), )

            api.save(folder)

            tg.flash(_('Folder created'), CST.STATUS_OK)
            redirect_url = redirect_url_tmpl.format(tmpl_context.workspace_id,
                                                    folder.content_id)
        except Exception as e:
            error_msg = 'An unexpected exception has been catched. ' \
                        'Look at the traceback below.'
            logger.error(self, error_msg)
            traceback.print_exc()

            tb = sys.exc_info()[2]
            tg.flash(
                _('Folder not created: {}').format(e.with_traceback(tb)),
                CST.STATUS_ERROR)
            if parent_id:
                redirect_url = \
                    redirect_url_tmpl.format(tmpl_context.workspace_id,
                                             parent_id)
            else:
                redirect_url = \
                    '/workspaces/{}'.format(tmpl_context.workspace_id)

        ####
        #
        # INFO - D.A. - 2014-10-22 - Do not put redirect in a
        # try/except block as redirect is using exceptions!
        #
        tg.redirect(tg.url(redirect_url))

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put(self,
            folder_id,
            label,
            can_contain_folders=False,
            can_contain_threads=False,
            can_contain_files=False,
            can_contain_pages=False):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)
        next_url = ''

        try:
            folder = api.get_one(int(folder_id), ContentType.Folder, workspace)
            subcontent = dict(
                folder=True if can_contain_folders == 'on' else False,
                thread=True if can_contain_threads == 'on' else False,
                file=True if can_contain_files == 'on' else False,
                page=True if can_contain_pages == 'on' else False)
            with new_revision(folder):
                if label != folder.label:
                    # TODO - D.A. - 2015-05-25
                    # Allow to set folder description
                    api.update_content(folder, label, folder.description)
                api.set_allowed_content(folder, subcontent)

                if not self._path_validation.validate_new_content(folder):
                    return render_invalid_integrity_chosen_path(
                        folder.get_label(), )

                api.save(folder)

            tg.flash(_('Folder updated'), CST.STATUS_OK)

            next_url = self.url(folder.content_id)

        except Exception as e:
            tg.flash(
                _('Folder not updated: {}').format(str(e)), CST.STATUS_ERROR)
            next_url = self.url(int(folder_id))

        tg.redirect(next_url)

    @property
    def _std_url(self):
        return tg.url('/workspaces/{}/folders/{}')

    @property
    def _parent_url(self):
        return tg.url('/workspaces/{}')

    @property
    def _item_type_label(self):
        return _('Folder')

    @property
    def _item_type(self):
        return ContentType.Folder

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_archive(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)
        item = content_api.get_one(item_id, self._item_type,
                                   tmpl_context.workspace)
        try:
            next_url = self._parent_url.format(item.workspace_id,
                                               item.parent_id)
            tmp_url = self._std_url.format(item.workspace_id, item.content_id)
            undo_url = tmp_url + '/put_archive_undo'
            archived_msg = '{} archived. ' \
                           '<a class="alert-link" href="{}">Cancel action</a>'
            msg = _(archived_msg).format(self._item_type_label, undo_url)
            with new_revision(item):
                content_api.archive(item)
                content_api.save(item, ActionDescription.ARCHIVING)
            # TODO allow to come back
            tg.flash(msg, CST.STATUS_OK, no_escape=True)
            tg.redirect(next_url)
        except ValueError as e:
            next_url = self._std_url.format(item.workspace_id, item.parent_id,
                                            item.content_id)
            msg = _('{} not archived: {}').format(self._item_type_label,
                                                  str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(next_url)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_archive_undo(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        # Here we do not filter deleted items
        content_api = ContentApi(tmpl_context.current_user, True, True)
        item = content_api.get_one(item_id, self._item_type,
                                   tmpl_context.workspace)
        try:
            next_url = self._std_url.format(item.workspace_id, item.content_id)
            msg = _('{} unarchived.').format(self._item_type_label)
            with new_revision(item):
                content_api.unarchive(item)
                content_api.save(item, ActionDescription.UNARCHIVING)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(next_url)

        except ValueError as e:
            msg = _('{} not un-archived: {}').format(self._item_type_label,
                                                     str(e))
            next_url = self._std_url.format(item.workspace_id, item.content_id)
            # We still use std url because the item has not been archived
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(next_url)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_delete(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)
        item = content_api.get_one(item_id, self._item_type,
                                   tmpl_context.workspace)
        try:

            next_url = self._parent_url.format(item.workspace_id,
                                               item.parent_id)
            tmp_url = self._std_url.format(item.workspace_id, item.content_id)
            undo_url = tmp_url + '/put_delete_undo'
            deleted_msg = '{} deleted. ' \
                          '<a class="alert-link" href="{}">Cancel action</a>'
            msg = _(deleted_msg).format(self._item_type_label, undo_url)
            with new_revision(item):
                content_api.delete(item)
                content_api.save(item, ActionDescription.DELETION)

            tg.flash(msg, CST.STATUS_OK, no_escape=True)
            tg.redirect(next_url)

        except ValueError as e:
            back_url = self._std_url.format(item.workspace_id, item.content_id)
            msg = _('{} not deleted: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(back_url)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_delete_undo(self, item_id):
        # TODO - CHECK RIGHTS

        item_id = int(item_id)
        # Here we do not filter deleted items
        content_api = ContentApi(tmpl_context.current_user, True, True)
        item = content_api.get_one(item_id, self._item_type,
                                   tmpl_context.workspace)
        try:
            next_url = self._std_url.format(item.workspace_id, item.content_id)
            msg = _('{} undeleted.').format(self._item_type_label)
            with new_revision(item):
                content_api.undelete(item)
                content_api.save(item, ActionDescription.UNDELETION)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(next_url)

        except ValueError as e:
            logger.debug(self, 'Exception: {}'.format(e.__str__))
            back_url = self._parent_url.format(item.workspace_id,
                                               item.parent_id)
            msg = _('{} not un-deleted: {}').format(self._item_type_label,
                                                    str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(back_url)
コード例 #3
0
ファイル: __init__.py プロジェクト: qyqx/tracim
class TIMWorkspaceContentRestController(TIMRestControllerWithBreadcrumb):
    """
    This class is intended to be parent class of controllers managing routes like
    /dashboard/workspaces/{}/folders/{}/someitems/{}
    """
    def _before(self, *args, **kw):
        try:
            TIMRestPathContextSetup.current_user()
            TIMRestPathContextSetup.current_workspace()
            TIMRestPathContextSetup.current_folder()
        except NoResultFound:
            abort(404)

    @property
    def _std_url(self):
        raise NotImplementedError('You must implement this method in child controllers')
        ##
        # ## Example of result:
        ## return tg.url('/dashboard/workspaces/{}/folders/{}/threads/{}')


    @property
    def _err_url(self):
        raise NotImplementedError('You must implement this method in child controllers')
        ##
        # ## Example of result:
        ## return tg.url('/dashboard/workspaces/{}/folders/{}/threads/{}')


    def _parent_url(self):
        raise NotImplementedError('You must implement this method in child controllers')
        ##
        # ## Example of result:
        ## return tg.url('/dashboard/workspaces/{}/folders/{}')


    @property
    def _item_type(self):
        raise NotImplementedError('You must implement this method in child controllers')


    @property
    def _item_type_label(self):
        raise NotImplementedError('You must implement this method in child controllers')


    @property
    def _get_one_context(self) -> str:
        """
        This method should return the get_all context associated to the given Node type
        example: CTX.THREAD
        """
        raise NotImplementedError('You must implement this method in child controllers')


    @property
    def _get_all_context(self) -> str:
        """
        This method should return the get_all context associated to the given Node type
        example: CTX.THREADS
        """
        raise NotImplementedError('You must implement this method in child controllers')

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def new(self, parent_id=None, workspace_id=None):
        """ Show the add form
         Note: parent is the /folders/{parent_id} value
         When refactoring urls, this may be need somme update
        """
        tg.override_template(self.new, self.TEMPLATE_NEW)

        workspace_id = tg.request.GET['workspace_id']
        parent_id = tg.request.GET['parent_id'] if 'parent_id' in tg.request.GET else None

        return DictLikeClass(result=self._struct_new_serialized(workspace_id, parent_id))

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def edit(self, item_id):
        """
        Show the edit form (do not really edit the data)

        :param item_id:
        :return:
        """

        # the follwing line allow to define the template to use in child classes.
        tg.override_template(self.edit, self.TEMPLATE_EDIT)

        item_id = int(item_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        content_api = ContentApi(user)
        item = content_api.get_one(item_id, self._item_type, workspace)

        dictified_item = Context(self._get_one_context).toDict(item, 'item')
        return DictLikeClass(result = dictified_item)

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def put(self, item_id, label='',content=''):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        try:
            api = ContentApi(tmpl_context.current_user)
            item = api.get_one(int(item_id), self._item_type, workspace)
            with new_revision(item):
                api.update_content(item, label, content)

                if not self._path_validation.validate_new_content(item):
                    return render_invalid_integrity_chosen_path(
                        item.get_label(),
                    )

                api.save(item, ActionDescription.REVISION)

            msg = _('{} updated').format(self._item_type_label)
            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.content_id))

        except SameValueError as e:
            msg = _('{} not updated: the content did not change').format(self._item_type_label)
            tg.flash(msg, CST.STATUS_WARNING)
            tg.redirect(self._err_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item_id))

        except ValueError as e:
            msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(self._err_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item_id))


    @tg.require(current_user_is_contributor())
    @tg.expose()
    def put_status(self, item_id, status):
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
        try:
            with new_revision(item):
                content_api.set_status(item, status)
                content_api.save(item, ActionDescription.STATUS_UPDATE)
            msg = _('{} status updated').format(self._item_type_label)
            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(self._std_url.format(item.workspace_id, item.parent_id, item.content_id))
        except ValueError as e:
            msg = _('{} status not updated: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(self._err_url.format(item.workspace_id, item.parent_id, item.content_id))


    def get_all_fake(self, context_workspace: Workspace, context_folder: Content) -> [Content]:
        """
        fake methods are used in other controllers in order to simulate a client/server api.
        the "client" controller method will include the result into its own fake_api object
        which will be available in the templates

        :param context_workspace: the workspace which would be taken from tmpl_context if we were in the normal behavior
        :return:
        """
        workspace = context_workspace
        content_api = ContentApi(tmpl_context.current_user)
        items = content_api.get_all(context_folder.content_id, self._item_type, workspace)

        dictified_items = Context(self._get_all_context).toDict(items)
        return DictLikeClass(result = dictified_items)


    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_archive(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
        try:
            next_url = self._parent_url.format(item.workspace_id, item.parent_id)
            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)+'/put_archive_undo'
            msg = _('{} archived. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)

            with new_revision(item):
                content_api.archive(item)
                content_api.save(item, ActionDescription.ARCHIVING)

            tg.flash(msg, CST.STATUS_OK, no_escape=True) # TODO allow to come back
            tg.redirect(next_url)
        except ValueError as e:
            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
            msg = _('{} not archived: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(next_url)

    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_archive_undo(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user, True) # Here we do not filter archived items
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
        try:
            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
            msg = _('{} unarchived.').format(self._item_type_label)
            with new_revision(item):
                content_api.unarchive(item)
                content_api.save(item, ActionDescription.UNARCHIVING)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(next_url )

        except ValueError as e:
            msg = _('{} not un-archived: {}').format(self._item_type_label, str(e))
            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
            # We still use std url because the item has not been archived
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(next_url)


    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_delete(self, item_id):
        # TODO - CHECK RIGHTS
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
        try:

            next_url = self._parent_url.format(item.workspace_id, item.parent_id)
            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)+'/put_delete_undo'
            msg = _('{} deleted. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
            with new_revision(item):
                content_api.delete(item)
                content_api.save(item, ActionDescription.DELETION)

            tg.flash(msg, CST.STATUS_OK, no_escape=True)
            tg.redirect(next_url)

        except ValueError as e:
            back_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
            msg = _('{} not deleted: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(back_url)


    @tg.require(current_user_is_content_manager())
    @tg.expose()
    def put_delete_undo(self, item_id):
        # TODO - CHECK RIGHTS

        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
        try:
            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
            msg = _('{} undeleted.').format(self._item_type_label)
            with new_revision(item):
                content_api.undelete(item)
                content_api.save(item, ActionDescription.UNDELETION)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(next_url)

        except ValueError as e:
            logger.debug(self, 'Exception: {}'.format(e.__str__))
            back_url = self._parent_url.format(item.workspace_id, item.parent_id)
            msg = _('{} not un-deleted: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(back_url)

    @tg.expose()
    @tg.require(not_anonymous())
    def put_read(self, item_id):
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)

        item_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)

        try:
            msg = _('{} marked as read.').format(self._item_type_label)
            content_api.mark_read(item)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(item_url)

        except ValueError as e:
            logger.debug(self, 'Exception: {}'.format(e.__str__))
            msg = _('{} not marked as read: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(item_url)

    @tg.expose()
    @tg.require(not_anonymous())
    def put_unread(self, item_id):
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
        item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)

        item_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)

        try:
            msg = _('{} marked unread.').format(self._item_type_label)
            content_api.mark_unread(item)

            tg.flash(msg, CST.STATUS_OK)
            tg.redirect(item_url)

        except ValueError as e:
            logger.debug(self, 'Exception: {}'.format(e.__str__))
            msg = _('{} not marked unread: {}').format(self._item_type_label, str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(item_url)