Exemple #1
0
    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))
Exemple #2
0
    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)
Exemple #3
0
    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
            )
            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)
            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)
Exemple #4
0
    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))
Exemple #5
0
    def test_search_in_description(self):
        # HACK - D.A. - 2015-03-09
        # This test is based on a bug which does NOT return results found
        # at root of a workspace (eg a folder)

        uapi = UserApi(None)
        groups = [GroupApi(None).get_one(Group.TIM_USER),
                  GroupApi(None).get_one(Group.TIM_MANAGER),
                  GroupApi(None).get_one(Group.TIM_ADMIN)]

        user = uapi.create_user(email='this.is@user',
                                groups=groups, save_now=True)

        workspace = WorkspaceApi(user).create_workspace('test workspace',
                                                        save_now=True)

        api = ContentApi(user)
        a = api.create(ContentType.Folder, workspace, None,
                       'this is randomized folder', True)
        p = api.create(ContentType.Page, workspace, a,
                       'this is dummy label content', True)

        with new_revision(p):
            p.description = 'This is some amazing test'

        api.save(p)
        original_id = p.content_id

        res = api.search(['dummy'])
        eq_(1, len(res.all()))
        item = res.all()[0]
        eq_(original_id, item.content_id)
Exemple #6
0
    def test_search_in_description(self):
        # HACK - D.A. - 2015-03-09
        # This test is based on a bug which does NOT return results found
        # at root of a workspace (eg a folder)

        uapi = UserApi(None)
        groups = [
            GroupApi(None).get_one(Group.TIM_USER),
            GroupApi(None).get_one(Group.TIM_MANAGER),
            GroupApi(None).get_one(Group.TIM_ADMIN)
        ]

        user = uapi.create_user(email='this.is@user',
                                groups=groups,
                                save_now=True)

        workspace = WorkspaceApi(user).create_workspace('test workspace',
                                                        save_now=True)

        api = ContentApi(user)
        a = api.create(ContentType.Folder, workspace, None,
                       'this is randomized folder', True)
        p = api.create(ContentType.Page, workspace, a,
                       'this is dummy label content', True)

        with new_revision(p):
            p.description = 'This is some amazing test'

        api.save(p)
        original_id = p.content_id

        res = api.search(['dummy'])
        eq_(1, len(res.all()))
        item = res.all()[0]
        eq_(original_id, item.content_id)
Exemple #7
0
    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)
Exemple #8
0
    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.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)
Exemple #9
0
    def put_delete_undo(self, item_id):
        require_current_user_is_owner(int(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)
        try:
            next_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id,
                                                                             tmpl_context.folder_id,
                                                                             tmpl_context.thread_id)
            msg = _('{} undeleted.').format(self._item_type_label)
            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('/workspaces/{}/folders/{}/threads/{}').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)
Exemple #10
0
    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)

        try:

            next_url = tg.url('/workspaces/{}/folders/{}/threads/{}').format(tmpl_context.workspace_id,
                                                                             tmpl_context.folder_id,
                                                                             tmpl_context.thread_id)
            undo_url = tg.url('/workspaces/{}/folders/{}/threads/{}/comments/{}/put_delete_undo').format(tmpl_context.workspace_id,
                                                                                                         tmpl_context.folder_id,
                                                                                                         tmpl_context.thread_id,
                                                                                                         item_id)

            msg = _('{} deleted. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
            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('/workspaces/{}/folders/{}/threads/{}').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)
Exemple #11
0
    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)
Exemple #12
0
    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)
Exemple #13
0
    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))
Exemple #14
0
    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)
Exemple #15
0
    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)
                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))
Exemple #16
0
    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)
            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)
            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:
            logger.error(
                self,
                'An unexpected exception has been catched. Look at the traceback below.'
            )
            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))
Exemple #17
0
    def post(self, label='', content=''):
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        page = api.create(ContentType.Page, workspace, tmpl_context.folder, label)
        page.description = content
        api.save(page, ActionDescription.CREATION, do_notify=True)

        tg.flash(_('Page created'), CST.STATUS_OK)
        tg.redirect(tg.url('/workspaces/{}/folders/{}/pages/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, page.content_id))
Exemple #18
0
    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))
Exemple #19
0
 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:
         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))
Exemple #20
0
    def test_search_in_label_or_description(self):
        # HACK - D.A. - 2015-03-09
        # This test is based on a bug which does NOT return results found
        # at root of a workspace (eg a folder)

        uapi = UserApi(None)
        groups = [GroupApi(None).get_one(Group.TIM_USER),
                  GroupApi(None).get_one(Group.TIM_MANAGER),
                  GroupApi(None).get_one(Group.TIM_ADMIN)]

        user = uapi.create_user(email='this.is@user',
                                groups=groups, save_now=True)

        workspace = WorkspaceApi(user).create_workspace('test workspace',
                                                        save_now=True)


        api = ContentApi(user)

        a = api.create(ContentType.Folder, workspace, None,
                       'this is randomized folder', True)
        p1 = api.create(ContentType.Page, workspace, a,
                        'this is dummy label content', True)
        p2 = api.create(ContentType.Page, workspace, a, 'Hey ! Jon !', True)

        with new_revision(p1):
            p1.description = 'This is some amazing test'

        with new_revision(p2):
            p2.description = 'What\'s up ?'

        api.save(p1)
        api.save(p2)

        id1 = p1.content_id
        id2 = p2.content_id

        eq_(1, DBSession.query(Workspace).filter(Workspace.label == 'test workspace').count())
        eq_(1, DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.label == 'this is randomized folder').count())
        eq_(2, DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.label == 'this is dummy label content').count())
        eq_(1, DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.description == 'This is some amazing test').count())
        eq_(2, DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.label == 'Hey ! Jon !').count())
        eq_(1, DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.description == 'What\'s up ?').count())

        res = api.search(['dummy', 'jon'])
        eq_(2, len(res.all()))

        eq_(True, id1 in [o.content_id for o in res.all()])
        eq_(True, id2 in [o.content_id for o in res.all()])
Exemple #21
0
 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))
Exemple #22
0
    def post(self, label='', content=''):
        workspace = tmpl_context.workspace

        api = ContentApi(tmpl_context.current_user)

        page = api.create(ContentType.Page, workspace, tmpl_context.folder,
                          label)
        page.description = content
        api.save(page, ActionDescription.CREATION, do_notify=True)

        tg.flash(_('Page created'), CST.STATUS_OK)
        tg.redirect(
            tg.url('/workspaces/{}/folders/{}/pages/{}').format(
                tmpl_context.workspace_id, tmpl_context.folder_id,
                page.content_id))
Exemple #23
0
    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))
Exemple #24
0
    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

            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))
Exemple #25
0
    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)
            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)
            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:
            logger.error(self, 'An unexpected exception has been catched. Look at the traceback below.')
            traceback.print_exc()

            tg.flash(_('Folder not created: {}').format(e.with_traceback()), 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))
Exemple #26
0
    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)
Exemple #27
0
    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)

            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)
Exemple #28
0
    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)
            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)
Exemple #29
0
    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))
Exemple #30
0
    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)
            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)
Exemple #31
0
    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))
Exemple #32
0
    def put_status(self, item_id, status):
        item_id = int(item_id)
        content_api = ContentApi(tmpl_context.current_user)

        try:
            item = content_api.get_one(item_id, self._item_type,
                                       tmpl_context.workspace)
            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))
        except NoResultFound as e:
            # probably the content is deleted or archived => forbidden to update status
            content_api = ContentApi(
                tmpl_context.current_user,
                show_archived=True,
                show_deleted=True
            )
            item = content_api.get_one(
                item_id,
                self._item_type,
                tmpl_context.workspace
            )

            next_url = self._std_url.format(
                item.workspace_id, item.parent_id, item.content_id
            )
            msg = _('{} status not updated: the operation '
                    'is not allowed on deleted/archived content').format(
                self._item_type_label
            )
            tg.flash(msg, CST.STATUS_ERROR)
            tg.redirect(next_url)
Exemple #33
0
    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)
Exemple #34
0
    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)
            api.update_content(item, label, content)
            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))
Exemple #35
0
    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))
Exemple #36
0
class File(DAVNonCollection):
    """
    File resource corresponding to tracim's files
    """
    def __init__(self, path: str, environ: dict, content: Content):
        super(File, self).__init__(path, environ)

        self.content = content
        self.user = UserApi(None).get_one_by_email(
            environ['http_authenticator.username'])
        self.content_api = ContentApi(self.user)

        # this is the property that windows client except to check if the file is read-write or read-only,
        # but i wasn't able to set this property so you'll have to look into it >.>
        # self.setPropertyValue('Win32FileAttributes', '00000021')

    def getPreferredPath(self):
        fix_txt = '.txt' if self.getContentType(
        ) == 'text/plain' else mimetypes.guess_extension(self.getContentType())

        if self.content.label == '' or self.path.endswith(fix_txt):
            return self.path
        else:
            return self.path + fix_txt

    def __repr__(self) -> str:
        return "<DAVNonCollection: File (%d)>" % self.content.revision_id

    def getContentLength(self) -> int:
        return len(self.content.file_content)

    def getContentType(self) -> str:
        return self.content.file_mimetype

    def getCreationDate(self) -> float:
        return mktime(self.content.created.timetuple())

    def getDisplayName(self) -> str:
        return self.content.get_label()

    def getLastModified(self) -> float:
        return mktime(self.content.updated.timetuple())

    def getContent(self):
        filestream = compat.BytesIO()
        filestream.write(self.content.file_content)
        filestream.seek(0)

        return filestream

    def beginWrite(self, contentType: str = None) -> FakeFileStream:
        return FakeFileStream(content=self.content,
                              content_api=self.content_api,
                              file_name=self.content.get_label(),
                              workspace=self.content.workspace,
                              path=self.path)

    def moveRecursive(self, destpath):
        """As we support recursive move, copymovesingle won't be called, though with copy it'll be called
            but i have to check if the client ever call that function..."""
        destpath = normpath(destpath)

        invalid_path = False

        # if content is either deleted or archived, we'll check that we try moving it to the parent
        # if yes, then we'll unarchive / undelete them, else the action's not allowed
        if self.content.is_deleted or self.content.is_archived:
            # we remove all archived and deleted from the path and we check to the destpath
            # has to be equal or else path not valid
            # ex: /a/b/.deleted/resource, to be valid destpath has to be = /a/b/resource (no other solution)
            current_path = re.sub(r'/\.(deleted|archived)', '', self.path)

            if current_path == destpath:
                ManageActions(
                    ActionDescription.UNDELETION if self.content.is_deleted
                    else ActionDescription.UNARCHIVING, self.content_api,
                    self.content).action()
            else:
                invalid_path = True
        # if the content is not deleted / archived, check if we're trying to delete / archive it by
        # moving it to a .deleted / .archived folder
        elif basename(dirname(destpath)) in ['.deleted', '.archived']:
            # same test as above ^
            dest_path = re.sub(r'/\.(deleted|archived)', '', destpath)

            if dest_path == self.path:
                ManageActions(
                    ActionDescription.DELETION
                    if '.deleted' in destpath else ActionDescription.ARCHIVING,
                    self.content_api, self.content).action()
            else:
                invalid_path = True
        # else we check if the path is good (not at the root path / not in a deleted/archived path)
        # and we move the content
        else:
            invalid_path = any(x in destpath
                               for x in ['.deleted', '.archived'])
            invalid_path = invalid_path or any(
                x in self.path for x in ['.deleted', '.archived'])
            invalid_path = invalid_path or dirname(
                destpath) == self.environ['http_authenticator.realm']

            if not invalid_path:
                self.move_file(destpath)

        if invalid_path:
            raise DAVError(HTTP_FORBIDDEN)

    def move_file(self, destpath):

        workspace = self.provider.get_workspace_from_path(
            normpath(destpath), WorkspaceApi(self.user))

        parent = self.provider.get_parent_from_path(normpath(destpath),
                                                    self.content_api,
                                                    workspace)

        with new_revision(self.content):
            if basename(destpath) != self.getDisplayName():
                self.content_api.update_content(
                    self.content,
                    re.sub('\.[^\.]+$', '',
                           self.provider.transform_to_bdd(basename(destpath))))
                self.content_api.save(self.content)
            else:
                self.content_api.move(item=self.content,
                                      new_parent=parent,
                                      must_stay_in_same_workspace=False,
                                      new_workspace=workspace)

        transaction.commit()

    def supportRecursiveMove(self, destPath):
        return True

    def delete(self):
        ManageActions(ActionDescription.DELETION, self.content_api,
                      self.content).action()
Exemple #37
0
class Workspace(DAVCollection):
    """
    Workspace resource corresponding to tracim's workspaces.
    Direct children can only be folders, though files might come later on and are supported
    """
    def __init__(self, path: str, environ: dict, workspace: data.Workspace):
        super(Workspace, self).__init__(path, environ)

        self.workspace = workspace
        self.content = None
        self.user = UserApi(None).get_one_by_email(
            environ['http_authenticator.username'])

        self.content_api = ContentApi(self.user, show_temporary=True)

        self._file_count = 0

    def __repr__(self) -> str:
        return "<DAVCollection: Workspace (%d)>" % self.workspace.workspace_id

    def getPreferredPath(self):
        return self.path

    def getCreationDate(self) -> float:
        return mktime(self.workspace.created.timetuple())

    def getDisplayName(self) -> str:
        return self.workspace.label

    def getLastModified(self) -> float:
        print("hm....", self.path)
        return mktime(self.workspace.updated.timetuple())

    def getMemberNames(self) -> [str]:
        retlist = []

        children = self.content_api.get_all(
            parent_id=self.content.id if self.content is not None else None,
            workspace=self.workspace)

        for content in children:
            # the purpose is to display .history only if there's at least one content's type that has a history
            if content.type != ContentType.Folder:
                self._file_count += 1
            retlist.append(content.get_label())

        return retlist

    def getMember(self, content_label: str) -> _DAVResource:

        return self.provider.getResourceInst(
            '%s/%s' %
            (self.path, self.provider.transform_to_display(content_label)),
            self.environ)

    def createEmptyResource(self, file_name: str):
        """
        [For now] we don't allow to create files right under workspaces.
        Though if we come to allow it, deleting the error's raise will make it possible.
        """
        # TODO : remove commentary here raise DAVError(HTTP_FORBIDDEN)
        if '/.deleted/' in self.path or '/.archived/' in self.path:
            raise DAVError(HTTP_FORBIDDEN)

        return FakeFileStream(file_name=file_name,
                              content_api=self.content_api,
                              workspace=self.workspace,
                              content=None,
                              parent=self.content,
                              path=self.path + '/' + file_name)

    def createCollection(self, label: str) -> 'Folder':
        """
        Create a new folder for the current workspace. As it's not possible for the user to choose
        which types of content are allowed in this folder, we allow allow all of them.

        This method return the DAVCollection created.
        """

        if '/.deleted/' in self.path or '/.archived/' in self.path:
            raise DAVError(HTTP_FORBIDDEN)

        folder = self.content_api.create(content_type=ContentType.Folder,
                                         workspace=self.workspace,
                                         label=label,
                                         parent=self.content)

        subcontent = dict(folder=True, thread=True, file=True, page=True)

        self.content_api.set_allowed_content(folder, subcontent)
        self.content_api.save(folder)

        transaction.commit()

        return Folder(
            '%s/%s' % (self.path, self.provider.transform_to_display(label)),
            self.environ, folder, self.workspace)

    def delete(self):
        """For now, it is not possible to delete a workspace through the webdav client."""
        raise DAVError(HTTP_FORBIDDEN)

    def supportRecursiveMove(self, destpath):
        return True

    def moveRecursive(self, destpath):
        if dirname(normpath(
                destpath)) == self.environ['http_authenticator.realm']:
            self.workspace.label = basename(normpath(destpath))
            transaction.commit()
        else:
            raise DAVError(HTTP_FORBIDDEN)

    def getMemberList(self) -> [_DAVResource]:
        members = []

        children = self.content_api.get_all(None, ContentType.Any,
                                            self.workspace)

        for content in children:
            content_path = '%s/%s' % (self.path,
                                      self.provider.transform_to_display(
                                          content.get_label()))

            if content.type == ContentType.Folder:
                members.append(
                    Folder(content_path, self.environ, self.workspace,
                           content))
            elif content.type == ContentType.File:
                self._file_count += 1
                members.append(File(content_path, self.environ, content))
            else:
                self._file_count += 1
                members.append(OtherFile(content_path, self.environ, content))

        if self._file_count > 0 and self.provider.show_history():
            members.append(
                HistoryFolder(path=self.path + '/' + ".history",
                              environ=self.environ,
                              content=self.content,
                              workspace=self.workspace,
                              type=HistoryType.Standard))

        if self.provider.show_delete():
            members.append(
                DeletedFolder(path=self.path + '/' + ".deleted",
                              environ=self.environ,
                              content=self.content,
                              workspace=self.workspace))

        if self.provider.show_archive():
            members.append(
                ArchivedFolder(path=self.path + '/' + ".archived",
                               environ=self.environ,
                               content=self.content,
                               workspace=self.workspace))

        return members
Exemple #38
0
class Workspace(DAVCollection):
    """
    Workspace resource corresponding to tracim's workspaces.
    Direct children can only be folders, though files might come later on and are supported
    """

    def __init__(self, path: str, environ: dict, workspace: data.Workspace):
        super(Workspace, self).__init__(path, environ)

        self.workspace = workspace
        self.content = None
        self.user = UserApi(None).get_one_by_email(environ['http_authenticator.username'])

        self.content_api = ContentApi(self.user, show_temporary=True)

        self._file_count = 0

    def __repr__(self) -> str:
        return "<DAVCollection: Workspace (%d)>" % self.workspace.workspace_id

    def getPreferredPath(self):
        return self.path

    def getCreationDate(self) -> float:
        return mktime(self.workspace.created.timetuple())

    def getDisplayName(self) -> str:
        return self.workspace.label

    def getLastModified(self) -> float:
        return mktime(self.workspace.updated.timetuple())

    def getMemberNames(self) -> [str]:
        retlist = []

        children = self.content_api.get_all(
            parent_id=self.content.id if self.content is not None else None,
            workspace=self.workspace
        )

        for content in children:
            # the purpose is to display .history only if there's at least one content's type that has a history
            if content.type != ContentType.Folder:
                self._file_count += 1
            retlist.append(content.get_label_as_file())

        return retlist

    def getMember(self, content_label: str) -> _DAVResource:

        return self.provider.getResourceInst(
            '%s/%s' % (self.path, transform_to_display(content_label)),
            self.environ
        )

    def createEmptyResource(self, file_name: str):
        """
        [For now] we don't allow to create files right under workspaces.
        Though if we come to allow it, deleting the error's raise will make it possible.
        """
        # TODO : remove commentary here raise DAVError(HTTP_FORBIDDEN)
        if '/.deleted/' in self.path or '/.archived/' in self.path:
            raise DAVError(HTTP_FORBIDDEN)

        content = None

        # Note: To prevent bugs, check here again if resource already exist
        path = os.path.join(self.path, file_name)
        resource = self.provider.getResourceInst(path, self.environ)
        if resource:
            content = resource.content

        return FakeFileStream(
            file_name=file_name,
            content_api=self.content_api,
            workspace=self.workspace,
            content=content,
            parent=self.content,
            path=self.path + '/' + file_name
        )

    def createCollection(self, label: str) -> 'Folder':
        """
        Create a new folder for the current workspace. As it's not possible for the user to choose
        which types of content are allowed in this folder, we allow allow all of them.

        This method return the DAVCollection created.
        """

        if '/.deleted/' in self.path or '/.archived/' in self.path:
            raise DAVError(HTTP_FORBIDDEN)

        folder = self.content_api.create(
            content_type=ContentType.Folder,
            workspace=self.workspace,
            label=label,
            parent=self.content
        )

        subcontent = dict(
            folder=True,
            thread=True,
            file=True,
            page=True
        )

        self.content_api.set_allowed_content(folder, subcontent)
        self.content_api.save(folder)

        transaction.commit()

        return Folder('%s/%s' % (self.path, transform_to_display(label)),
                      self.environ, folder,
                      self.workspace)

    def delete(self):
        """For now, it is not possible to delete a workspace through the webdav client."""
        raise DAVError(HTTP_FORBIDDEN)

    def supportRecursiveMove(self, destpath):
        return True

    def moveRecursive(self, destpath):
        if dirname(normpath(destpath)) == self.environ['http_authenticator.realm']:
            self.workspace.label = basename(normpath(destpath))
            transaction.commit()
        else:
            raise DAVError(HTTP_FORBIDDEN)

    def getMemberList(self) -> [_DAVResource]:
        members = []

        children = self.content_api.get_all(False, ContentType.Any, self.workspace)

        for content in children:
            content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))

            if content.type == ContentType.Folder:
                members.append(Folder(content_path, self.environ, self.workspace, content))
            elif content.type == ContentType.File:
                self._file_count += 1
                members.append(File(content_path, self.environ, content))
            else:
                self._file_count += 1
                members.append(OtherFile(content_path, self.environ, content))

        if self._file_count > 0 and self.provider.show_history():
            members.append(
                HistoryFolder(
                    path=self.path + '/' + ".history",
                    environ=self.environ,
                    content=self.content,
                    workspace=self.workspace,
                    type=HistoryType.Standard
                )
            )

        if self.provider.show_delete():
            members.append(
                DeletedFolder(
                    path=self.path + '/' + ".deleted",
                    environ=self.environ,
                    content=self.content,
                    workspace=self.workspace
                )
            )

        if self.provider.show_archive():
            members.append(
                ArchivedFolder(
                    path=self.path + '/' + ".archived",
                    environ=self.environ,
                    content=self.content,
                    workspace=self.workspace
                )
            )

        return members
Exemple #39
0
    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))
Exemple #40
0
    def test_update_file_data(self):
        uapi = UserApi(None)
        groups = [
            GroupApi(None).get_one(Group.TIM_USER),
            GroupApi(None).get_one(Group.TIM_MANAGER),
            GroupApi(None).get_one(Group.TIM_ADMIN)
        ]

        user1 = uapi.create_user(email='this.is@user',
                                 groups=groups,
                                 save_now=True)

        workspace = WorkspaceApi(user1).create_workspace('test workspace',
                                                         save_now=True)
        wid = workspace.workspace_id

        user2 = uapi.create_user()
        user2.email = '*****@*****.**'
        uapi.save(user2)

        RoleApi(user1).create_one(user2,
                                  workspace,
                                  UserRoleInWorkspace.CONTENT_MANAGER,
                                  with_notif=True,
                                  flush=True)

        # Test starts here

        api = ContentApi(user1)
        p = api.create(ContentType.File, workspace, None, 'this_is_a_page',
                       True)

        u1id = user1.user_id
        u2id = user2.user_id
        pcid = p.content_id
        poid = p.owner_id

        api.save(p)
        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        api = ContentApi(user1)

        content = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u1id, content.owner_id)
        eq_(poid, content.owner_id)

        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2)
        content2 = api2.get_one(pcid, ContentType.Any, workspace)
        with new_revision(content2):
            api2.update_file_data(content2, 'index.html', 'text/html',
                                  b'<html>hello world</html>')
        api2.save(content2)
        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)

        updated = api.get_one(pcid, ContentType.Any, workspace)
        eq_(
            u2id, updated.owner_id,
            'the owner id should be {} (found {})'.format(
                u2id, updated.owner_id))
        eq_('index.html', updated.file_name)
        eq_('text/html', updated.file_mimetype)
        eq_(b'<html>hello world</html>', updated.file_content)
        eq_(ActionDescription.REVISION, updated.revision_type)
Exemple #41
0
class File(DAVNonCollection):
    """
    File resource corresponding to tracim's files
    """
    def __init__(self, path: str, environ: dict, content: Content):
        super(File, self).__init__(path, environ)

        self.content = content
        self.user = UserApi(None).get_one_by_email(environ['http_authenticator.username'])
        self.content_api = ContentApi(self.user)

        # this is the property that windows client except to check if the file is read-write or read-only,
        # but i wasn't able to set this property so you'll have to look into it >.>
        # self.setPropertyValue('Win32FileAttributes', '00000021')

    def __repr__(self) -> str:
        return "<DAVNonCollection: File (%d)>" % self.content.revision_id

    def getContentLength(self) -> int:
        return self.content.depot_file.file.content_length

    def getContentType(self) -> str:
        return self.content.file_mimetype

    def getCreationDate(self) -> float:
        return mktime(self.content.created.timetuple())

    def getDisplayName(self) -> str:
        return self.content.file_name

    def getLastModified(self) -> float:
        return mktime(self.content.updated.timetuple())

    def getContent(self) -> typing.BinaryIO:
        filestream = compat.BytesIO()
        filestream.write(self.content.depot_file.file.read())
        filestream.seek(0)

        return filestream

    def beginWrite(self, contentType: str=None) -> FakeFileStream:
        return FakeFileStream(
            content=self.content,
            content_api=self.content_api,
            file_name=self.content.get_label_as_file(),
            workspace=self.content.workspace,
            path=self.path
        )

    def moveRecursive(self, destpath):
        """As we support recursive move, copymovesingle won't be called, though with copy it'll be called
            but i have to check if the client ever call that function..."""
        destpath = normpath(destpath)

        invalid_path = False

        # if content is either deleted or archived, we'll check that we try moving it to the parent
        # if yes, then we'll unarchive / undelete them, else the action's not allowed
        if self.content.is_deleted or self.content.is_archived:
            # we remove all archived and deleted from the path and we check to the destpath
            # has to be equal or else path not valid
            # ex: /a/b/.deleted/resource, to be valid destpath has to be = /a/b/resource (no other solution)
            current_path = re.sub(r'/\.(deleted|archived)', '', self.path)

            if current_path == destpath:
                ManageActions(
                    ActionDescription.UNDELETION if self.content.is_deleted else ActionDescription.UNARCHIVING,
                    self.content_api,
                    self.content
                ).action()
            else:
                invalid_path = True
        # if the content is not deleted / archived, check if we're trying to delete / archive it by
        # moving it to a .deleted / .archived folder
        elif basename(dirname(destpath)) in ['.deleted', '.archived']:
            # same test as above ^
            dest_path = re.sub(r'/\.(deleted|archived)', '', destpath)

            if dest_path == self.path:
                ManageActions(
                    ActionDescription.DELETION if '.deleted' in destpath else ActionDescription.ARCHIVING,
                    self.content_api,
                    self.content
                ).action()
            else:
                invalid_path = True
        # else we check if the path is good (not at the root path / not in a deleted/archived path)
        # and we move the content
        else:
            invalid_path = any(x in destpath for x in ['.deleted', '.archived'])
            invalid_path = invalid_path or any(x in self.path for x in ['.deleted', '.archived'])
            invalid_path = invalid_path or dirname(destpath) == self.environ['http_authenticator.realm']

            if not invalid_path:
                self.move_file(destpath)

        if invalid_path:
            raise DAVError(HTTP_FORBIDDEN)

    def move_file(self, destpath):

        workspace = self.content.workspace
        parent = self.content.parent

        with new_revision(self.content):
            if basename(destpath) != self.getDisplayName():
                new_given_file_name = transform_to_bdd(basename(destpath))
                new_file_name, new_file_extension = \
                    os.path.splitext(new_given_file_name)

                self.content_api.update_content(
                    self.content,
                    new_file_name,
                )
                self.content.file_extension = new_file_extension
                self.content_api.save(self.content)
            else:
                workspace_api = WorkspaceApi(self.user)
                content_api = ContentApi(self.user)

                destination_workspace = self.provider.get_workspace_from_path(
                    destpath,
                    workspace_api,
                )

                destination_parent = self.provider.get_parent_from_path(
                    destpath,
                    content_api,
                    destination_workspace,
                )

                self.content_api.move(
                    item=self.content,
                    new_parent=destination_parent,
                    must_stay_in_same_workspace=False,
                    new_workspace=destination_workspace
                )

        transaction.commit()

    def supportRecursiveMove(self, destPath):
        return True

    def delete(self):
        ManageActions(ActionDescription.DELETION, self.content_api, self.content).action()
Exemple #42
0
    def test_delete_undelete(self):
        uapi = UserApi(None)
        groups = [
            GroupApi(None).get_one(Group.TIM_USER),
            GroupApi(None).get_one(Group.TIM_MANAGER),
            GroupApi(None).get_one(Group.TIM_ADMIN)
        ]

        user1 = uapi.create_user(email='this.is@user',
                                 groups=groups,
                                 save_now=True)
        u1id = user1.user_id

        workspace = WorkspaceApi(user1).create_workspace('test workspace',
                                                         save_now=True)
        wid = workspace.workspace_id

        user2 = uapi.create_user()
        user2.email = '*****@*****.**'
        uapi.save(user2)

        RoleApi(user1).create_one(user2,
                                  workspace,
                                  UserRoleInWorkspace.CONTENT_MANAGER,
                                  with_notif=True,
                                  flush=True)

        # show archived is used at the top end of the test
        api = ContentApi(user1, show_deleted=True)
        p = api.create(ContentType.File, workspace, None, 'this_is_a_page',
                       True)

        u1id = user1.user_id
        u2id = user2.user_id
        pcid = p.content_id
        poid = p.owner_id

        transaction.commit()

        ####
        user1 = UserApi(None).get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)

        content = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u1id, content.owner_id)
        eq_(poid, content.owner_id)

        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2, show_deleted=True)
        content2 = api2.get_one(pcid, ContentType.Any, workspace)
        with new_revision(content2):
            api2.delete(content2)
        api2.save(content2)
        transaction.commit()

        ####

        user1 = UserApi(None).get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        # show archived is used at the top end of the test
        api = ContentApi(user1, show_deleted=True)
        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2, show_deleted=True)

        updated = api2.get_one(pcid, ContentType.Any, workspace)
        eq_(
            u2id, updated.owner_id,
            'the owner id should be {} (found {})'.format(
                u2id, updated.owner_id))
        eq_(True, updated.is_deleted)
        eq_(ActionDescription.DELETION, updated.revision_type)

        ####

        updated2 = api.get_one(pcid, ContentType.Any, workspace)
        with new_revision(updated2):
            api.undelete(updated2)
        api.save(updated2)
        eq_(False, updated2.is_deleted)
        eq_(ActionDescription.UNDELETION, updated2.revision_type)
        eq_(u1id, updated2.owner_id)
Exemple #43
0
    def test_search_in_label_or_description(self):
        # HACK - D.A. - 2015-03-09
        # This test is based on a bug which does NOT return results found
        # at root of a workspace (eg a folder)

        uapi = UserApi(None)
        groups = [
            GroupApi(None).get_one(Group.TIM_USER),
            GroupApi(None).get_one(Group.TIM_MANAGER),
            GroupApi(None).get_one(Group.TIM_ADMIN)
        ]

        user = uapi.create_user(email='this.is@user',
                                groups=groups,
                                save_now=True)

        workspace = WorkspaceApi(user).create_workspace('test workspace',
                                                        save_now=True)

        api = ContentApi(user)

        a = api.create(ContentType.Folder, workspace, None,
                       'this is randomized folder', True)
        p1 = api.create(ContentType.Page, workspace, a,
                        'this is dummy label content', True)
        p2 = api.create(ContentType.Page, workspace, a, 'Hey ! Jon !', True)

        with new_revision(p1):
            p1.description = 'This is some amazing test'

        with new_revision(p2):
            p2.description = 'What\'s up ?'

        api.save(p1)
        api.save(p2)

        id1 = p1.content_id
        id2 = p2.content_id

        eq_(
            1,
            DBSession.query(Workspace).filter(
                Workspace.label == 'test workspace').count())
        eq_(
            1,
            DBSession.query(ContentRevisionRO).filter(
                ContentRevisionRO.label ==
                'this is randomized folder').count())
        eq_(
            2,
            DBSession.query(ContentRevisionRO).filter(
                ContentRevisionRO.label ==
                'this is dummy label content').count())
        eq_(
            1,
            DBSession.query(ContentRevisionRO).filter(
                ContentRevisionRO.description ==
                'This is some amazing test').count())
        eq_(
            2,
            DBSession.query(ContentRevisionRO).filter(
                ContentRevisionRO.label == 'Hey ! Jon !').count())
        eq_(
            1,
            DBSession.query(ContentRevisionRO).filter(
                ContentRevisionRO.description == 'What\'s up ?').count())

        res = api.search(['dummy', 'jon'])
        eq_(2, len(res.all()))

        eq_(True, id1 in [o.content_id for o in res.all()])
        eq_(True, id2 in [o.content_id for o in res.all()])
Exemple #44
0
    def test_update(self):
        uapi = UserApi(None)
        groups = [GroupApi(None).get_one(Group.TIM_USER),
                  GroupApi(None).get_one(Group.TIM_MANAGER),
                  GroupApi(None).get_one(Group.TIM_ADMIN)]

        user1 = uapi.create_user(email='this.is@user',
                                groups=groups, save_now=True)

        workspace = WorkspaceApi(user1).create_workspace('test workspace',
                                                        save_now=True)
        wid = workspace.workspace_id

        user2 = uapi.create_user()
        user2.email = '*****@*****.**'
        uapi.save(user2)

        RoleApi(user1).create_one(user2, workspace,
                                  UserRoleInWorkspace.CONTENT_MANAGER,
                                  with_notif=False,
                                  flush=True)

        # Test starts here

        api = ContentApi(user1)
        p = api.create(ContentType.Page, workspace, None,
                       'this_is_a_page', True)

        u1id = user1.user_id
        u2id = user2.user_id
        pcid = p.content_id
        poid = p.owner_id

        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        api = ContentApi(user1)

        content = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u1id, content.owner_id)
        eq_(poid, content.owner_id)

        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2)
        content2 = api2.get_one(pcid, ContentType.Any, workspace)
        with new_revision(content2):
            api2.update_content(content2, 'this is an updated page', 'new content')
        api2.save(content2)
        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        api = ContentApi(user1)

        updated = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u2id, updated.owner_id,
            'the owner id should be {} (found {})'.format(u2id,
                                                          updated.owner_id))
        eq_('this is an updated page', updated.label)
        eq_('new content', updated.description)
        eq_(ActionDescription.EDITION, updated.revision_type)
Exemple #45
0
    def test_delete_undelete(self):
        uapi = UserApi(None)
        groups = [GroupApi(None).get_one(Group.TIM_USER),
                  GroupApi(None).get_one(Group.TIM_MANAGER),
                  GroupApi(None).get_one(Group.TIM_ADMIN)]

        user1 = uapi.create_user(email='this.is@user',
                                groups=groups, save_now=True)
        u1id = user1.user_id

        workspace = WorkspaceApi(user1).create_workspace('test workspace',
                                                        save_now=True)
        wid = workspace.workspace_id

        user2 = uapi.create_user()
        user2.email = '*****@*****.**'
        uapi.save(user2)

        RoleApi(user1).create_one(user2, workspace,
                                  UserRoleInWorkspace.CONTENT_MANAGER,
                                  with_notif=True,
                                  flush=True)

        # show archived is used at the top end of the test
        api = ContentApi(user1, show_deleted=True)
        p = api.create(ContentType.File, workspace, None,
                       'this_is_a_page', True)

        u1id = user1.user_id
        u2id = user2.user_id
        pcid = p.content_id
        poid = p.owner_id

        transaction.commit()

        ####
        user1 = UserApi(None).get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)

        content = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u1id, content.owner_id)
        eq_(poid, content.owner_id)

        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2, show_deleted=True)
        content2 = api2.get_one(pcid, ContentType.Any, workspace)
        with new_revision(content2):
            api2.delete(content2)
        api2.save(content2)
        transaction.commit()

        ####

        user1 = UserApi(None).get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        # show archived is used at the top end of the test
        api = ContentApi(user1, show_deleted=True)
        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2, show_deleted=True)

        updated = api2.get_one(pcid, ContentType.Any, workspace)
        eq_(u2id, updated.owner_id,
            'the owner id should be {} (found {})'.format(u2id,
                                                          updated.owner_id))
        eq_(True, updated.is_deleted)
        eq_(ActionDescription.DELETION, updated.revision_type)

        ####

        updated2 = api.get_one(pcid, ContentType.Any, workspace)
        with new_revision(updated2):
            api.undelete(updated2)
        api.save(updated2)
        eq_(False, updated2.is_deleted)
        eq_(ActionDescription.UNDELETION, updated2.revision_type)
        eq_(u1id, updated2.owner_id)
Exemple #46
0
    def test_update_file_data(self):
        uapi = UserApi(None)
        groups = [GroupApi(None).get_one(Group.TIM_USER),
                  GroupApi(None).get_one(Group.TIM_MANAGER),
                  GroupApi(None).get_one(Group.TIM_ADMIN)]

        user1 = uapi.create_user(email='this.is@user',
                                groups=groups, save_now=True)

        workspace = WorkspaceApi(user1).create_workspace('test workspace',
                                                        save_now=True)
        wid = workspace.workspace_id

        user2 = uapi.create_user()
        user2.email = '*****@*****.**'
        uapi.save(user2)

        RoleApi(user1).create_one(user2, workspace,
                                  UserRoleInWorkspace.CONTENT_MANAGER,
                                  with_notif=True,
                                  flush=True)

        # Test starts here

        api = ContentApi(user1)
        p = api.create(ContentType.File, workspace, None,
                       'this_is_a_page', True)

        u1id = user1.user_id
        u2id = user2.user_id
        pcid = p.content_id
        poid = p.owner_id

        api.save(p)
        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)
        api = ContentApi(user1)

        content = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u1id, content.owner_id)
        eq_(poid, content.owner_id)

        u2 = UserApi(None).get_one(u2id)
        api2 = ContentApi(u2)
        content2 = api2.get_one(pcid, ContentType.Any, workspace)
        with new_revision(content2):
            api2.update_file_data(content2, 'index.html', 'text/html',
                                  b'<html>hello world</html>')
        api2.save(content2)
        transaction.commit()

        # Refresh instances after commit
        user1 = uapi.get_one(u1id)
        workspace = WorkspaceApi(user1).get_one(wid)

        updated = api.get_one(pcid, ContentType.Any, workspace)
        eq_(u2id, updated.owner_id,
            'the owner id should be {} (found {})'.format(u2id,
                                                          updated.owner_id))
        eq_('this_is_a_page.html', updated.file_name)
        eq_('text/html', updated.file_mimetype)
        eq_(b'<html>hello world</html>', updated.file_content)
        eq_(ActionDescription.REVISION, updated.revision_type)