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))
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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))
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)
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))
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))
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))
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))
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))
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()])
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 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))
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))
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))
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))
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)
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)
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)
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))
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)
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))
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)
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)
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))
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))
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()
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
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
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))
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)
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()
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)
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()])
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)
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)
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)