示例#1
0
文件: __init__.py 项目: qyqx/tracim
class TIMRestControllerWithBreadcrumb(TIMRestController):

    def get_breadcrumb(self, item_id=None) -> [BreadcrumbItem]:
        """
        TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
        :param item_id: an item id (item may be normal content or folder
        :return:
        """
        return ContentApi(
            tmpl_context.current_user,
            show_archived=True,
            show_deleted=True,
        ).build_breadcrumb(tmpl_context.workspace, item_id)

    def _struct_new_serialized(self, workspace_id, parent_id):
        print('values are: ', workspace_id, parent_id)
        result = DictLikeClass(
            item=DictLikeClass(parent=DictLikeClass(id=parent_id),
                               workspace=DictLikeClass(id=workspace_id)))

        return result

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def new(self, parent_id=None, workspace_id=None):
        """ Show the add form """
        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))
示例#2
0
文件: content.py 项目: qyqx/tracim
class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController
                                              ):
    """
    manage a path like this: /workspaces/1/folders/XXX/pages/4
    """

    TEMPLATE_NEW = 'mako:tracim.templates.thread.new'
    TEMPLATE_EDIT = 'mako:tracim.templates.thread.edit'

    comments = UserWorkspaceFolderThreadCommentRestController()

    def _before(self, *args, **kw):
        TIMRestPathContextSetup.current_user()
        TIMRestPathContextSetup.current_workspace()
        TIMRestPathContextSetup.current_folder()

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

    @property
    def _err_url(self):
        return self._std_url

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

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

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

    @property
    def _get_one_context(self) -> str:
        return CTX.THREAD

    @property
    def _get_all_context(self) -> str:
        return CTX.THREADS

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def post(self, label='', content='', parent_id=None):
        """
        Creates a new thread. Actually, on POST, the content will be included
        in a user comment instead of being the thread description
        :param label:
        :param content:
        :return:
        """
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        with DBSession.no_autoflush:
            thread = api.create(ContentType.Thread, workspace,
                                tmpl_context.folder, label)
            # FIXME - DO NOT DUPLCIATE FIRST MESSAGE
            # thread.description = content
            api.save(thread, ActionDescription.CREATION, do_notify=False)

            comment = api.create(ContentType.Comment, workspace, thread, label)
            comment.label = ''
            comment.description = content

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

        api.save(comment, ActionDescription.COMMENT, do_notify=False)
        api.do_notify(thread)

        tg.flash(_('Thread created'), CST.STATUS_OK)
        tg.redirect(
            self._std_url.format(tmpl_context.workspace_id,
                                 tmpl_context.folder_id, thread.content_id))

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.thread.getone')
    def get_one(self, thread_id, **kwargs):
        """
        :param thread_id: content_id of Thread
        :param inverted: fill with True equivalent to invert order of comments
                         NOTE: This parameter is in kwargs because prevent URL
                         changes.
        """
        inverted = kwargs.get('inverted')
        thread_id = int(thread_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

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

        content_api = ContentApi(
            user,
            show_deleted=True,
            show_archived=True,
        )
        thread = content_api.get_one(thread_id, ContentType.Thread, workspace)

        fake_api_breadcrumb = self.get_breadcrumb(thread_id)
        fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb,
                                         current_user=current_user_content)
        fake_api = Context(CTX.FOLDER).toDict(fake_api_content)

        dictified_thread = Context(CTX.THREAD).toDict(thread, 'thread')

        if inverted:
            dictified_thread.thread.history = \
                reversed(dictified_thread.thread.history)

        return DictLikeClass(
            result=dictified_thread,
            fake_api=fake_api,
            inverted=inverted,
        )
示例#3
0
文件: content.py 项目: qyqx/tracim
class UserWorkspaceFolderThreadCommentRestController(TIMRestController):
    @property
    def _item_type(self):
        return ContentType.Comment

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

    def _before(self, *args, **kw):
        TIMRestPathContextSetup.current_user()
        TIMRestPathContextSetup.current_workspace()
        TIMRestPathContextSetup.current_folder()
        TIMRestPathContextSetup.current_thread()

    @tg.expose()
    @tg.require(current_user_is_contributor())
    def post(self, content: str = ''):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace
        thread = tmpl_context.thread

        api = ContentApi(tmpl_context.current_user)

        comment = api.create_comment(workspace, thread, content, True)
        next_str = '/workspaces/{}/folders/{}/threads/{}'
        next_url = tg.url(next_str).format(tmpl_context.workspace_id,
                                           tmpl_context.folder_id,
                                           tmpl_context.thread_id)
        tg.flash(_('Comment added'), CST.STATUS_OK)
        tg.redirect(next_url)

    @tg.expose()
    @tg.require(not_anonymous())
    def put_delete(self, item_id):
        require_current_user_is_owner(int(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)
        next_or_back = '/workspaces/{}/folders/{}/threads/{}'
        try:
            next_url = tg.url(next_or_back).format(tmpl_context.workspace_id,
                                                   tmpl_context.folder_id,
                                                   tmpl_context.thread_id)
            undo_str = '{}/comments/{}/put_delete_undo'
            undo_url = tg.url(undo_str).format(next_url, item_id)
            msg_str = ('{} deleted. '
                       '<a class="alert-link" href="{}">Cancel action</a>')
            msg = _(msg_str).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 = tg.url(next_or_back).format(tmpl_context.workspace_id,
                                                   tmpl_context.folder_id,
                                                   tmpl_context.thread_id)
            msg = _('{} not 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_delete_undo(self, item_id):
        require_current_user_is_owner(int(item_id))

        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)
        next_or_back = '/workspaces/{}/folders/{}/threads/{}'
        try:
            next_url = tg.url(next_or_back).format(tmpl_context.workspace_id,
                                                   tmpl_context.folder_id,
                                                   tmpl_context.thread_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 = tg.url(next_or_back).format(tmpl_context.workspace_id,
                                                   tmpl_context.folder_id,
                                                   tmpl_context.thread_id)
            msg = _('{} not un-deleted: {}').format(self._item_type_label,
                                                    str(e))
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(back_url)
示例#4
0
文件: content.py 项目: qyqx/tracim
class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
    """
    manage a path like this: /workspaces/1/folders/XXX/pages/4
    """

    TEMPLATE_NEW = 'mako:tracim.templates.page.new'
    TEMPLATE_EDIT = 'mako:tracim.templates.page.edit'

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

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

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

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

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

    @property
    def _get_one_context(self) -> str:
        return CTX.PAGE

    @property
    def _get_all_context(self) -> str:
        return CTX.PAGES

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.page.getone')
    def get_one(self, page_id, revision_id=None):
        page_id = int(page_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

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

        content_api = ContentApi(
            user,
            show_deleted=True,
            show_archived=True,
        )
        if revision_id:
            page = content_api.get_one_from_revision(page_id, ContentType.Page,
                                                     workspace, revision_id)
        else:
            page = content_api.get_one(page_id, ContentType.Page, workspace)

        fake_api_breadcrumb = self.get_breadcrumb(page_id)
        fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb,
                                         current_user=current_user_content)
        fake_api = Context(CTX.FOLDER).toDict(fake_api_content)

        dictified_page = Context(CTX.PAGE).toDict(page, 'page')
        return DictLikeClass(result=dictified_page, fake_api=fake_api)

    def get_all_fake(self, context_workspace: Workspace,
                     context_folder: 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)
        pages = content_api.get_all(context_folder.content_id,
                                    ContentType.Page, workspace)

        dictified_pages = Context(CTX.PAGES).toDict(pages)
        return DictLikeClass(result=dictified_pages)

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def post(self, label='', content=''):
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        with DBSession.no_autoflush:
            page = api.create(ContentType.Page, workspace, tmpl_context.folder,
                              label)
            page.description = content

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

        api.save(page, ActionDescription.CREATION, do_notify=True)

        tg.flash(_('Page created'), CST.STATUS_OK)
        redirect = '/workspaces/{}/folders/{}/pages/{}'
        tg.redirect(
            tg.url(redirect).format(tmpl_context.workspace_id,
                                    tmpl_context.folder_id, page.content_id))

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def put(self, item_id, label='', content=''):
        # INFO - D.A. This method is a raw copy of
        # 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:
            not_updated = '{} not updated: the content did not change'
            msg = _(not_updated).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:
            not_updated = '{} not updated - error: {}'
            msg = _(not_updated).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))
示例#5
0
文件: content.py 项目: qyqx/tracim
class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
    """
    manage a path like this: /workspaces/1/folders/XXX/files/4
    """

    TEMPLATE_NEW = 'mako:tracim.templates.file.new'
    TEMPLATE_EDIT = 'mako:tracim.templates.file.edit'

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

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

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

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

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

    @property
    def _get_one_context(self) -> str:
        return CTX.FILE

    @property
    def _get_all_context(self) -> str:
        return CTX.FILES

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.file.getone')
    def get_one(self, file_id, revision_id=None):
        file_id = int(file_id)
        cache_path = CFG.get_instance().PREVIEW_CACHE
        preview_manager = PreviewManager(cache_path, create_folder=True)
        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_archived=True, show_deleted=True)
        if revision_id:
            file = content_api.get_one_from_revision(file_id, self._item_type,
                                                     workspace, revision_id)
        else:
            file = content_api.get_one(file_id, self._item_type, workspace)
            revision_id = file.revision_id

        file_path = content_api.get_one_revision_filepath(revision_id)
        nb_page = preview_manager.get_nb_page(file_path=file_path)
        preview_urls = []
        for page in range(int(nb_page)):
            url_str = '/previews/{}/pages/{}?revision_id={}'
            url = url_str.format(file_id, page, revision_id)
            preview_urls.append(url)

        fake_api_breadcrumb = self.get_breadcrumb(file_id)
        fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb,
                                         current_user=current_user_content)
        fake_api = Context(CTX.FOLDER,
                           current_user=user).toDict(fake_api_content)
        dictified_file = Context(self._get_one_context,
                                 current_user=user).toDict(file, 'file')
        result = DictLikeClass(result=dictified_file,
                               fake_api=fake_api,
                               nb_page=nb_page,
                               url=preview_urls)
        return result

    @tg.require(current_user_is_reader())
    @tg.expose()
    def download(self, file_id, revision_id=None):
        file_id = int(file_id)
        revision_id = int(revision_id) if revision_id != 'latest' else None
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        content_api = ContentApi(user)
        revision_to_send = None
        if revision_id:
            item = content_api.get_one_from_revision(file_id, self._item_type,
                                                     workspace, revision_id)
        else:
            item = content_api.get_one(file_id, self._item_type, workspace)

        revision_to_send = None
        if item.revision_to_serialize <= 0:
            for revision in item.revisions:
                if not revision_to_send:
                    revision_to_send = revision

                if revision.revision_id > revision_to_send.revision_id:
                    revision_to_send = revision
        else:
            for revision in item.revisions:
                if revision.revision_id == item.revision_to_serialize:
                    revision_to_send = revision
                    break

        content_type = 'application/x-download'
        if revision_to_send.file_mimetype:
            content_type = str(revision_to_send.file_mimetype)
            tg.response.headers['Content-type'] = \
                str(revision_to_send.file_mimetype)

        tg.response.headers['Content-Type'] = content_type
        file_name = get_valid_header_file_name(revision_to_send.file_name)
        tg.response.headers['Content-Disposition'] = \
            str('attachment; filename="{}"'.format(file_name))
        return DepotManager.get().get(revision_to_send.depot_file)

    def get_all_fake(self, context_workspace: Workspace,
                     context_folder: 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)
        files = content_api.get_all(context_folder.content_id,
                                    ContentType.File, workspace)

        dictified_files = Context(CTX.FILES).toDict(files)
        return DictLikeClass(result=dictified_files)

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def post(self, label='', file_data=None):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace
        folder = tmpl_context.folder

        api = ContentApi(tmpl_context.current_user)
        with DBSession.no_autoflush:
            file = api.create(ContentType.File, workspace, folder, label)
            api.update_file_data(file, file_data.filename, file_data.type,
                                 file_data.file.read())
            # Display error page to user if chosen label is in conflict
            if not self._path_validation.validate_new_content(file):
                return render_invalid_integrity_chosen_path(
                    file.get_label_as_file(), )
        api.save(file, ActionDescription.CREATION)

        tg.flash(_('File created'), CST.STATUS_OK)
        redirect = '/workspaces/{}/folders/{}/files/{}'
        tg.redirect(
            tg.url(redirect).format(tmpl_context.workspace_id,
                                    tmpl_context.folder_id, file.content_id))

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def put(self, item_id, file_data=None, comment=None, label=None):
        # 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)
            label_changed = False
            if label is not None and label != item.label:
                label_changed = True

            if label is None:
                label = ''

            # TODO - D.A. - 2015-03-19
            # refactor this method in order to make code easier to understand

            with new_revision(item):

                if (comment and label) or (not comment and label_changed):
                    updated_item = api.update_content(
                        item, label if label else item.label,
                        comment if comment else '')

                    # Display error page to user if chosen label is in conflict
                    if not self._path_validation.validate_new_content(
                            updated_item, ):
                        return render_invalid_integrity_chosen_path(
                            updated_item.get_label_as_file(), )

                    api.save(updated_item, ActionDescription.EDITION)

                    # This case is the default "file title and description
                    # update" In this case the file itself is not revisionned

                else:
                    # So, now we may have a comment and/or a file revision
                    if comment and '' == label:
                        comment_item = api.create_comment(workspace,
                                                          item,
                                                          comment,
                                                          do_save=False)

                        if not isinstance(file_data, FieldStorage):
                            api.save(comment_item, ActionDescription.COMMENT)
                        else:
                            # The notification is only sent
                            # if the file is NOT updated
                            #
                            # If the file is also updated,
                            # then a 'file revision' notification will be sent.
                            api.save(comment_item,
                                     ActionDescription.COMMENT,
                                     do_notify=False)

                    if isinstance(file_data, FieldStorage):
                        api.update_file_data(item, file_data.filename,
                                             file_data.type,
                                             file_data.file.read())

                        # Display error page to user if chosen label is in
                        # conflict
                        if not self._path_validation.validate_new_content(
                                item, ):
                            return render_invalid_integrity_chosen_path(
                                item.get_label_as_file(), )

                        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 ValueError as e:
            error = '{} not updated - error: {}'
            msg = _(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))
示例#6
0
class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController
                                              ):
    """
    manage a path like this: /workspaces/1/folders/XXX/pages/4
    """

    TEMPLATE_NEW = 'mako:tracim.templates.thread.new'
    TEMPLATE_EDIT = 'mako:tracim.templates.thread.edit'

    comments = UserWorkspaceFolderThreadCommentRestController()

    def _before(self, *args, **kw):
        TIMRestPathContextSetup.current_user()
        TIMRestPathContextSetup.current_workspace()
        TIMRestPathContextSetup.current_folder()

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

    @property
    def _err_url(self):
        return self._std_url

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

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

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

    @property
    def _get_one_context(self) -> str:
        return CTX.THREAD

    @property
    def _get_all_context(self) -> str:
        return CTX.THREADS

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def post(self, label='', content='', parent_id=None):
        """
        Creates a new thread. Actually, on POST, the content will be included in a user comment instead of being the thread description
        :param label:
        :param content:
        :return:
        """
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        thread = api.create(ContentType.Thread, workspace, tmpl_context.folder,
                            label)
        # FIXME - DO NOT DUPLCIATE FIRST MESSAGE thread.description = content
        api.save(thread, ActionDescription.CREATION, do_notify=False)

        comment = api.create(ContentType.Comment, workspace, thread, label)
        comment.label = ''
        comment.description = content
        api.save(comment, ActionDescription.COMMENT, do_notify=False)
        api.do_notify(thread)

        tg.flash(_('Thread created'), CST.STATUS_OK)
        tg.redirect(
            self._std_url.format(tmpl_context.workspace_id,
                                 tmpl_context.folder_id, thread.content_id))

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.thread.getone')
    def get_one(self, thread_id):
        thread_id = int(thread_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

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

        content_api = ContentApi(user)
        thread = content_api.get_one(thread_id, ContentType.Thread, workspace)

        fake_api_breadcrumb = self.get_breadcrumb(thread_id)
        fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb,
                                         current_user=current_user_content)
        fake_api = Context(CTX.FOLDER).toDict(fake_api_content)

        dictified_thread = Context(CTX.THREAD).toDict(thread, 'thread')
        return DictLikeClass(result=dictified_thread, fake_api=fake_api)
示例#7
0
class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
    """
    manage a path like this: /workspaces/1/folders/XXX/files/4
    """

    TEMPLATE_NEW = 'mako:tracim.templates.file.new'
    TEMPLATE_EDIT = 'mako:tracim.templates.file.edit'

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

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

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

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

    @property
    def _get_one_context(self) -> str:
        return CTX.FILE

    @property
    def _get_all_context(self) -> str:
        return CTX.FILES

    @tg.require(current_user_is_reader())
    @tg.expose('tracim.templates.file.getone')
    def get_one(self, file_id, revision_id=None):
        file_id = int(file_id)
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace
        workspace_id = tmpl_context.workspace_id

        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)
        if revision_id:
            file = content_api.get_one_from_revision(file_id, self._item_type,
                                                     workspace, revision_id)
        else:
            file = content_api.get_one(file_id, self._item_type, workspace)

        fake_api_breadcrumb = self.get_breadcrumb(file_id)
        fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb,
                                         current_user=current_user_content)
        fake_api = Context(CTX.FOLDER,
                           current_user=user).toDict(fake_api_content)

        dictified_file = Context(self._get_one_context,
                                 current_user=user).toDict(file, 'file')
        return DictLikeClass(result=dictified_file, fake_api=fake_api)

    @tg.require(current_user_is_reader())
    @tg.expose()
    def download(self, file_id, revision_id=None):
        file_id = int(file_id)
        revision_id = int(revision_id) if revision_id != 'latest' else None
        user = tmpl_context.current_user
        workspace = tmpl_context.workspace

        content_api = ContentApi(user)
        revision_to_send = None
        if revision_id:
            item = content_api.get_one_from_revision(file_id, self._item_type,
                                                     workspace, revision_id)
        else:
            item = content_api.get_one(file_id, self._item_type, workspace)

        revision_to_send = None
        if item.revision_to_serialize <= 0:
            for revision in item.revisions:
                if not revision_to_send:
                    revision_to_send = revision

                if revision.revision_id > revision_to_send.revision_id:
                    revision_to_send = revision
        else:
            for revision in item.revisions:
                if revision.revision_id == item.revision_to_serialize:
                    revision_to_send = revision
                    break

        content_type = 'application/x-download'
        if revision_to_send.file_mimetype:
            content_type = str(revision_to_send.file_mimetype)
            tg.response.headers['Content-type'] = str(
                revision_to_send.file_mimetype)

        tg.response.headers['Content-Type'] = content_type
        tg.response.headers['Content-Disposition'] = str(
            'attachment; filename="{}"'.format(revision_to_send.file_name))
        return revision_to_send.file_content

    def get_all_fake(self, context_workspace: Workspace,
                     context_folder: 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)
        files = content_api.get_all(context_folder.content_id,
                                    ContentType.File, workspace)

        dictified_files = Context(CTX.FILES).toDict(files)
        return DictLikeClass(result=dictified_files)

    @tg.require(current_user_is_contributor())
    @tg.expose()
    def post(self, label='', file_data=None):
        # TODO - SECURE THIS
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        file = api.create(ContentType.File, workspace, tmpl_context.folder,
                          label)
        api.update_file_data(file, file_data.filename, file_data.type,
                             file_data.file.read())
        api.save(file, ActionDescription.CREATION)

        tg.flash(_('File created'), CST.STATUS_OK)
        tg.redirect(
            tg.url('/workspaces/{}/folders/{}/files/{}').format(
                tmpl_context.workspace_id, tmpl_context.folder_id,
                file.content_id))

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

        try:
            item_saved = False
            api = ContentApi(tmpl_context.current_user)
            item = api.get_one(int(item_id), self._item_type, workspace)

            # TODO - D.A. - 2015-03-19
            # refactor this method in order to make code easier to understand

            with new_revision(item):

                if comment and label:
                    updated_item = api.update_content(
                        item, label if label else item.label,
                        comment if comment else '')
                    api.save(updated_item, ActionDescription.EDITION)

                    # This case is the default "file title and description update"
                    # In this case the file itself is not revisionned

                else:
                    # So, now we may have a comment and/or a file revision
                    if comment and '' == label:
                        comment_item = api.create_comment(workspace,
                                                          item,
                                                          comment,
                                                          do_save=False)

                        if not isinstance(file_data, FieldStorage):
                            api.save(comment_item, ActionDescription.COMMENT)
                        else:
                            # The notification is only sent
                            # if the file is NOT updated
                            #
                            #  If the file is also updated,
                            #  then a 'file revision' notification will be sent.
                            api.save(comment_item,
                                     ActionDescription.COMMENT,
                                     do_notify=False)

                    if isinstance(file_data, FieldStorage):
                        api.update_file_data(item, file_data.filename,
                                             file_data.type,
                                             file_data.file.read())
                        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 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))
示例#8
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)