def test_api___simple_search_ok__no_search_string(self) -> None: dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter(User.email == "*****@*****.**").one() uapi = UserApi(current_user=admin, session=dbsession, config=self.app_config) gapi = GroupApi(current_user=admin, session=dbsession, config=self.app_config) groups = [gapi.get_one_with_name("trusted-users")] user = uapi.create_user( "*****@*****.**", password="******", do_save=True, do_notify=False, groups=groups, ) workspace_api = WorkspaceApi( current_user=admin, session=dbsession, config=self.app_config, show_deleted=True ) workspace = workspace_api.create_workspace("test", save_now=True) rapi = RoleApi(current_user=admin, session=dbsession, config=self.app_config) rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False) api = ContentApi(session=dbsession, current_user=user, config=self.app_config) api.create( content_type_slug="html-document", workspace=workspace, label="test", do_save=True ) transaction.commit() self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200) search_result = res.json_body assert search_result assert search_result["total_hits"] == 0 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 0
def test_func__create_new_content_with_notification__ok__nominal_case(self): uapi = UserApi(current_user=None, session=self.session, config=self.app_config) current_user = uapi.get_one_by_email("*****@*****.**") # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi(current_user=current_user, session=self.session, config=self.app_config) workspace = wapi.get_one_by_label("Recipes") user = uapi.get_one_by_email("*****@*****.**") wapi.enable_notifications(user, workspace) api = ContentApi(current_user=user, session=self.session, config=self.app_config) item = api.create( content_type_list.Folder.slug, workspace, None, "parent", do_save=True, do_notify=False ) api.create( content_type_list.File.slug, workspace, item, "file1", do_save=True, do_notify=True ) # Send mail async from redis queue with daemon daemon = MailSenderDaemon(self.app_config, burst=True) daemon.run() # check mail received response = self.get_mailhog_mails() headers = response[0]["Content"]["Headers"] assert headers["From"][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' assert headers["To"][0] == "Global manager <*****@*****.**>" assert headers["Subject"][0] == "[TRACIM] [Recipes] file1 (Open)" assert headers["References"][0] == "test_user_refs+22@localhost" assert ( headers["Reply-to"][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' )
def test_api___simple_search_ok__by_comment_content( self, created_content_name, search_string, nb_content_result, first_search_result_content_name, first_created_comment_content, second_created_comment_content, ) -> None: dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter(User.email == "*****@*****.**").one() uapi = UserApi(current_user=admin, session=dbsession, config=self.app_config) gapi = GroupApi(current_user=admin, session=dbsession, config=self.app_config) groups = [gapi.get_one_with_name("trusted-users")] user = uapi.create_user( "*****@*****.**", password="******", do_save=True, do_notify=False, groups=groups, ) workspace_api = WorkspaceApi( current_user=admin, session=dbsession, config=self.app_config, show_deleted=True ) workspace = workspace_api.create_workspace("test", save_now=True) rapi = RoleApi(current_user=admin, session=dbsession, config=self.app_config) rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False) api = ContentApi(session=dbsession, current_user=user, config=self.app_config) content = api.create( content_type_slug="html-document", workspace=workspace, label=created_content_name, do_save=True, ) api.create_comment( workspace=workspace, parent=content, content=first_created_comment_content, do_save=True ) api.create_comment( workspace=workspace, parent=content, content=second_created_comment_content, do_save=True, ) api.create( content_type_slug="html-document", workspace=workspace, label="report", do_save=True ) api.create( content_type_slug="thread", workspace=workspace, label="discussion", do_save=True ) transaction.commit() self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) params = {"search_string": search_string} res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == nb_content_result assert search_result["is_total_hits_accurate"] is False assert search_result["contents"][0]["label"] == first_search_result_content_name
def test_func__create_new_content_with_notification__ok__nominal_case( self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( content_type_list.Folder.slug, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( content_type_list.File.slug, workspace, item, 'file1', do_save=True, do_notify=True, ) # Send mail async from redis queue redis = get_redis_connection(self.app_config) queue = get_rq_queue( redis, 'mail_sender', ) worker = SimpleWorker([queue], connection=queue.connection) worker.work(burst=True) # check mail received response = requests.get('http://127.0.0.1:8025/api/v1/messages') response = response.json() headers = response[0]['Content']['Headers'] assert headers['From'][ 0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][ 0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def test_func__create_comment_with_notification__ok__nominal_case(self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # set admin as french, useful to verify if i18n work properly current_user.lang = 'fr' # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( content_type_list.Folder.slug, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( content_type_list.File.slug, workspace, item, 'file1', do_save=True, do_notify=False, ) api.create_comment(parent=item2, content='My super comment', do_save=True, do_notify=True) transaction.commit() # check mail received response = requests.get('http://127.0.0.1:8025/api/v1/messages') response = response.json() headers = response[0]['Content']['Headers'] assert headers['From'][ 0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][ 0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def test_func__create_new_content_with_notification__ok__nominal_case(self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( content_type_list.Folder.slug, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( content_type_list.File.slug, workspace, item, 'file1', do_save=True, do_notify=True, ) # Send mail async from redis queue redis = get_redis_connection( self.app_config ) queue = get_rq_queue( redis, 'mail_sender', ) worker = SimpleWorker([queue], connection=queue.connection) worker.work(burst=True) # check mail received response = self.get_mailhog_mails() headers = response[0]['Content']['Headers'] assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def test_func__create_new_content_with_notification__ok__nominal_case( self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( content_type_list.Folder.slug, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( content_type_list.File.slug, workspace, item, 'file1', do_save=True, do_notify=True, ) # Send mail async from redis queue with daemon daemon = MailSenderDaemon(self.app_config, burst=True) daemon.run() # check mail received response = self.get_mailhog_mails() headers = response[0]['Content']['Headers'] assert headers['From'][ 0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][ 0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def test_func__create_comment_with_notification__ok__nominal_case(self): uapi = UserApi( current_user=None, session=self.session, config=self.app_config, ) current_user = uapi.get_one_by_email('*****@*****.**') # set admin as french, useful to verify if i18n work properly current_user.lang = 'fr' # Create new user with notification enabled on w1 workspace wapi = WorkspaceApi( current_user=current_user, session=self.session, config=self.app_config, ) workspace = wapi.get_one_by_label('Recipes') user = uapi.get_one_by_email('*****@*****.**') wapi.enable_notifications(user, workspace) api = ContentApi( current_user=user, session=self.session, config=self.app_config, ) item = api.create( content_type_list.Folder.slug, workspace, None, 'parent', do_save=True, do_notify=False, ) item2 = api.create( content_type_list.File.slug, workspace, item, 'file1', do_save=True, do_notify=False, ) api.create_comment(parent=item2, content='My super comment', do_save=True, do_notify=True) transaction.commit() # check mail received response = self.get_mailhog_mails() headers = response[0]['Content']['Headers'] assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>' # nopep8 assert headers['To'][0] == 'Global manager <*****@*****.**>' assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)' assert headers['References'][0] == 'test_user_refs+22@localhost' assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>' # nopep8
def create_generic_empty_content(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ Creates a generic empty content. The minimum viable content has a label and a content type. Creating a content generally starts with a request to this endpoint. For specific contents like files, it is recommended to use the dedicated endpoint. This feature is accessible to contributors and higher role only. """ app_config = request.registry.settings["CFG"] # type: CFG creation_data = hapic_data.body api = ContentApi(current_user=request.current_user, session=request.dbsession, config=app_config) parent = None if creation_data.parent_id: try: parent = api.get_one(content_id=creation_data.parent_id, content_type=content_type_list.Any_SLUG) except ContentNotFound as exc: raise ParentNotFound( "Parent with content_id {} not found".format( creation_data.parent_id)) from exc content = api.create( label=creation_data.label, content_type_slug=creation_data.content_type, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) api.execute_created_content_actions(content) content = api.get_content_in_context(content) return content
def test_api__elasticsearch_search__ok__in_file_ingest_search(self): dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter( User.email == "*****@*****.**").one() uapi = UserApi(current_user=admin, session=dbsession, config=self.app_config) gapi = GroupApi(current_user=admin, session=dbsession, config=self.app_config) groups = [gapi.get_one_with_name("trusted-users")] user = uapi.create_user( "*****@*****.**", password="******", do_save=True, do_notify=False, groups=groups, ) workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config, show_deleted=True) workspace = workspace_api.create_workspace("test", save_now=True) rapi = RoleApi(current_user=admin, session=dbsession, config=self.app_config) rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False) api = ContentApi(session=dbsession, current_user=user, config=self.app_config) with self.session.no_autoflush: text_file = api.create( content_type_slug=content_type_list.File.slug, workspace=workspace, label="important", do_save=False, ) api.update_file_data(text_file, "test_file", "text/plain", b"we need to find stringtosearch here !") api.save(text_file) api.execute_created_content_actions(text_file) content_id = text_file.content_id transaction.commit() self.refresh_elasticsearch() params = {"search_string": "stringtosearch"} self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 1 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 1 assert search_result["contents"][0]["content_id"] == content_id
def test_api__post_content_comment__ok_200__nominal_case(self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() # type: models.User workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config) business_workspace = workspace_api.get_one(1) content_api = ContentApi(current_user=admin, session=dbsession, config=self.app_config) tool_folder = content_api.get_one( 1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label='Test Thread', do_save=True, do_notify=False, ) with new_revision( session=dbsession, tm=transaction.manager, content=test_thread, ): content_api.update_content(test_thread, new_label='test_thread_updated', new_content='Just a test') transaction.commit() self.testapp.authorization = ('Basic', ('*****@*****.**', '*****@*****.**')) params = {'raw_content': 'I strongly disagree, Tiramisu win!'} res = self.testapp.post_json( '/api/v2/workspaces/{}/contents/{}/comments'.format( business_workspace.workspace_id, test_thread.content_id), params=params, status=200) comment = res.json_body assert comment['content_id'] assert comment['parent_id'] == test_thread.content_id assert comment['raw_content'] == 'I strongly disagree, Tiramisu win!' assert comment['author'] assert comment['author']['user_id'] == admin.user_id # TODO - G.M - 2018-06-172 - [avatar] setup avatar url assert comment['author']['avatar_url'] is None assert comment['author']['public_name'] == admin.display_name # TODO - G.M - 2018-06-179 - better check for datetime assert comment['created']
def test_api__post_content_comment__ok_200__nominal_case(self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter( User.email == "*****@*****.**").one() # type: User workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config) business_workspace = workspace_api.get_one(1) content_api = ContentApi(current_user=admin, session=dbsession, config=self.app_config) tool_folder = content_api.get_one( 1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label="Test Thread", do_save=True, do_notify=False, ) with new_revision(session=dbsession, tm=transaction.manager, content=test_thread): content_api.update_content(test_thread, new_label="test_thread_updated", new_content="Just a test") transaction.commit() self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) params = {"raw_content": "I strongly disagree, Tiramisu win!"} res = self.testapp.post_json( "/api/v2/workspaces/{}/contents/{}/comments".format( business_workspace.workspace_id, test_thread.content_id), params=params, status=200, ) comment = res.json_body assert comment["content_id"] assert comment["parent_id"] == test_thread.content_id assert comment["raw_content"] == "I strongly disagree, Tiramisu win!" assert comment["author"] assert comment["author"]["user_id"] == admin.user_id # TODO - G.M - 2018-06-172 - [avatar] setup avatar url assert comment["author"]["avatar_url"] is None assert comment["author"]["public_name"] == admin.display_name # TODO - G.M - 2018-06-179 - better check for datetime assert comment["created"]
def create_file(self, context, request: TracimRequest, hapic_data=None): """ Create a file .This will create 2 new revision. """ # INFO - G.M - 2019-09-03 - check validation of file here, because marshmallow # required doesn't work correctly with cgi_fieldstorage. # check is done with None because cgi_fieldstorage cannot be converted to bool if hapic_data.files.files is None: raise NoFileValidationError( 'No file "files" given at input, validation failed.') app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) api.check_upload_size(request.content_length, request.current_workspace) _file = hapic_data.files.files parent_id = hapic_data.forms.parent_id parent = None # type: typing.Optional['Content'] if parent_id: try: parent = api.get_one(content_id=parent_id, content_type=content_type_list.Any_SLUG) except ContentNotFound as exc: raise ParentNotFound( "Parent with content_id {} not found".format( parent_id)) from exc content = api.create( filename=_file.filename, content_type_slug=FILE_TYPE, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) api.execute_created_content_actions(content) return api.get_content_in_context(content)
def create_file(self, context, request: TracimRequest, hapic_data=None): """ Create a file .This will create 2 new revision. """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) _file = hapic_data.files.files parent_id = hapic_data.forms.parent_id api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config ) parent = None # type: typing.Optional['Content'] if parent_id: try: parent = api.get_one(content_id=parent_id, content_type=content_type_list.Any_SLUG) # nopep8 except ContentNotFound as exc: raise ParentNotFound( 'Parent with content_id {} not found'.format(parent_id) ) from exc content = api.create( filename=_file.filename, content_type_slug=FILE_TYPE, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) return api.get_content_in_context(content)
def test_api__post_content_comment__err_400__content_not_editable( self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() # type: models.User workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config) business_workspace = workspace_api.get_one(1) content_api = ContentApi(current_user=admin, session=dbsession, config=self.app_config) tool_folder = content_api.get_one( 1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label='Test Thread', do_save=True, do_notify=False, ) with new_revision( session=dbsession, tm=transaction.manager, content=test_thread, ): content_api.update_content(test_thread, new_label='test_thread_updated', new_content='Just a test') content_api.set_status(test_thread, 'closed-deprecated') transaction.commit() self.testapp.authorization = ('Basic', ('*****@*****.**', '*****@*****.**')) params = {'raw_content': 'I strongly disagree, Tiramisu win!'} res = self.testapp.post_json( '/api/v2/workspaces/{}/contents/{}/comments'.format( business_workspace.workspace_id, test_thread.content_id), params=params, status=400) assert res.json_body assert 'code' in res.json_body assert res.json_body['code'] == error.CONTENT_IN_NOT_EDITABLE_STATE
def test_api__post_content_comment__err_400__content_not_editable( self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter( User.email == "*****@*****.**").one() # type: User workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config) business_workspace = workspace_api.get_one(1) content_api = ContentApi(current_user=admin, session=dbsession, config=self.app_config) tool_folder = content_api.get_one( 1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label="Test Thread", do_save=True, do_notify=False, ) with new_revision(session=dbsession, tm=transaction.manager, content=test_thread): content_api.update_content(test_thread, new_label="test_thread_updated", new_content="Just a test") content_api.set_status(test_thread, "closed-deprecated") transaction.commit() self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) params = {"raw_content": "I strongly disagree, Tiramisu win!"} res = self.testapp.post_json( "/api/v2/workspaces/{}/contents/{}/comments".format( business_workspace.workspace_id, test_thread.content_id), params=params, status=400, ) assert res.json_body assert "code" in res.json_body assert res.json_body["code"] == ErrorCode.CONTENT_IN_NOT_EDITABLE_STATE
def create_content( description: str, user_api_factory, workspace_api_factory, session, app_config, content_type: str = "html-document", parent_content: typing.Optional[Content] = None, ) -> Content: with transaction.manager: uapi = user_api_factory.get() try: user = uapi.get_one_by_email(email="this.is@user") except Exception: user = uapi.create_minimal_user(email="this.is@user", profile=Profile.ADMIN, save_now=True, username="******") if parent_content: workspace = parent_content.workspace else: workspace = workspace_api_factory.get(user).create_workspace( "test workspace", save_now=True) api = ContentApi(current_user=user, session=session, config=app_config) content = api.create( content_type_slug=content_type, workspace=workspace, parent=parent_content, label="Content", do_save=True, ) with new_revision(session=session, tm=transaction.manager, content=content): content.description = description api.update_content(content, new_label=content.label) api.save(content) return content
def create_file_from_template( self, context: DefaultRootFactory, request: TracimRequest, hapic_data: HapicData = None) -> ContentInContext: app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi(current_user=request.current_user, session=request.dbsession, config=app_config) collaborative_document_edition_api = CollaborativeDocumentEditionFactory( ).get_lib(current_user=request.current_user, session=request.dbsession, config=app_config) collaborative_document_edition_api.check_template_available( hapic_data.body.template) parent = None # type: typing.Optional['Content'] if hapic_data.body.parent_id: try: parent = api.get_one(content_id=hapic_data.body.parent_id, content_type=content_type_list.Any_SLUG) except ContentNotFound as exc: raise ParentNotFound( "Parent with content_id {} not found".format( hapic_data.body.parent_id)) from exc content = api.create( filename=hapic_data.body.filename, content_type_slug=FILE_TYPE, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): collaborative_document_edition_api.update_content_from_template( content=content, template_filename=hapic_data.body.template) api.execute_created_content_actions(content) return api.get_content_in_context(content)
def create_generic_empty_content( self, context, request: TracimRequest, hapic_data=None, ) -> ContentInContext: """ Creates a generic empty content. The minimum viable content has a label and a content type. Creating a content generally starts with a request to this endpoint. For specific contents like files, it is recommended to use the dedicated endpoint. This feature is accessible to contributors and higher role only. """ app_config = request.registry.settings['CFG'] creation_data = hapic_data.body api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config ) parent = None if creation_data.parent_id: try: parent = api.get_one(content_id=creation_data.parent_id, content_type=content_type_list.Any_SLUG) # nopep8 except ContentNotFound as exc: raise ParentNotFound( 'Parent with content_id {} not found'.format(creation_data.parent_id) ) from exc content = api.create( label=creation_data.label, content_type_slug=creation_data.content_type, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) content = api.get_content_in_context(content) return content
def insert(self): admin = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() bob = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() john_the_reader = self._session.query(models.User) \ .filter(models.User.email == '*****@*****.**') \ .one() admin_workspace_api = WorkspaceApi( current_user=admin, session=self._session, config=self._config, ) bob_workspace_api = WorkspaceApi(current_user=bob, session=self._session, config=self._config) content_api = ContentApi(current_user=admin, session=self._session, config=self._config) bob_content_api = ContentApi(current_user=bob, session=self._session, config=self._config) reader_content_api = ContentApi(current_user=john_the_reader, session=self._session, config=self._config) role_api = RoleApi( current_user=admin, session=self._session, config=self._config, ) # Workspaces business_workspace = admin_workspace_api.create_workspace( 'Business', description='All importants documents', save_now=True, ) recipe_workspace = admin_workspace_api.create_workspace( 'Recipes', description='Our best recipes', save_now=True, ) other_workspace = bob_workspace_api.create_workspace( 'Others', description='Other Workspace', save_now=True, ) # Workspaces roles role_api.create_one( user=bob, workspace=recipe_workspace, role_level=UserRoleInWorkspace.CONTENT_MANAGER, with_notif=False, ) role_api.create_one( user=john_the_reader, workspace=recipe_workspace, role_level=UserRoleInWorkspace.READER, with_notif=False, ) # Folders tool_workspace = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label='Tools', do_save=True, do_notify=False, ) menu_workspace = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label='Menus', do_save=True, do_notify=False, ) dessert_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Desserts', do_save=True, do_notify=False, ) salads_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Salads', do_save=True, do_notify=False, ) other_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=other_workspace, label='Infos', do_save=True, do_notify=False, ) # Pages, threads, .. tiramisu_page = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=dessert_folder, label='Tiramisu Recipes!!!', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): content_api.update_content( item=tiramisu_page, new_content= '<p>To cook a greet Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipes!!!', ) content_api.save(tiramisu_page) best_cake_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=recipe_workspace, parent=dessert_folder, label='Best Cake', do_save=False, do_notify=False, ) best_cake_thread.description = 'Which is the best cake?' self._session.add(best_cake_thread) apple_pie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label='Apple_Pie', do_save=False, do_notify=False, ) apple_pie_recipe.file_extension = '.txt' apple_pie_recipe.depot_file = FileIntent( b'Apple pie Recipe', 'apple_Pie.txt', 'text/plain', ) self._session.add(apple_pie_recipe) Brownie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label='Brownie Recipe', do_save=False, do_notify=False, ) Brownie_recipe.file_extension = '.html' Brownie_recipe.depot_file = FileIntent( b'<p>Brownie Recipe</p>', 'brownie_recipe.html', 'text/html', ) self._session.add(Brownie_recipe) fruits_desserts_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Fruits Desserts', parent=dessert_folder, do_save=True, ) menu_page = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=business_workspace, parent=menu_workspace, label='Current Menu', do_save=True, ) new_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Bad Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) # File at the root for test new_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='Bad Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) content_api.create_comment( parent=best_cake_thread, content= '<p>What is for you the best cake ever? </br> I personnally vote for Chocolate cupcake!</p>', # nopep8 do_save=True, ) bob_content_api.create_comment( parent=best_cake_thread, content='<p>What about Apple Pie? There are Awesome!</p>', do_save=True, ) reader_content_api.create_comment( parent=best_cake_thread, content= '<p>You are right, but Kouign-amann are clearly better.</p>', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=best_cake_thread, ): bob_content_api.update_content( item=best_cake_thread, new_content='What is the best cake?', new_label='Best Cakes?', ) bob_content_api.save(best_cake_thread) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): bob_content_api.update_content( item=tiramisu_page, new_content= '<p>To cook a great Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipe', ) bob_content_api.save(tiramisu_page) self._session.flush()
def insert(self): admin = self._session.query(User) \ .filter(User.email == '*****@*****.**') \ .one() bob = self._session.query(User) \ .filter(User.email == '*****@*****.**') \ .one() john_the_reader = self._session.query(User) \ .filter(User.email == '*****@*****.**') \ .one() admin_workspace_api = WorkspaceApi( current_user=admin, session=self._session, config=self._config, ) bob_workspace_api = WorkspaceApi( current_user=bob, session=self._session, config=self._config ) content_api = ContentApi( current_user=admin, session=self._session, config=self._config ) bob_content_api = ContentApi( current_user=bob, session=self._session, config=self._config ) reader_content_api = ContentApi( current_user=john_the_reader, session=self._session, config=self._config ) role_api = RoleApi( current_user=admin, session=self._session, config=self._config, ) # Workspaces business_workspace = admin_workspace_api.create_workspace( 'Business', description='All importants documents', save_now=True, ) recipe_workspace = admin_workspace_api.create_workspace( 'Recipes', description='Our best recipes', save_now=True, ) other_workspace = bob_workspace_api.create_workspace( 'Others', description='Other Workspace', save_now=True, ) # Workspaces roles role_api.create_one( user=bob, workspace=recipe_workspace, role_level=UserRoleInWorkspace.CONTENT_MANAGER, with_notif=False, ) role_api.create_one( user=john_the_reader, workspace=recipe_workspace, role_level=UserRoleInWorkspace.READER, with_notif=False, ) # Folders tool_workspace = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label='Tools', do_save=True, do_notify=False, ) menu_workspace = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label='Menus', do_save=True, do_notify=False, ) dessert_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Desserts', do_save=True, do_notify=False, ) salads_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Salads', do_save=True, do_notify=False, ) other_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=other_workspace, label='Infos', do_save=True, do_notify=False, ) # Pages, threads, .. tiramisu_page = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=dessert_folder, label='Tiramisu Recipes!!!', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): content_api.update_content( item=tiramisu_page, new_content='<p>To cook a greet Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipes!!!', ) content_api.save(tiramisu_page) best_cake_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=recipe_workspace, parent=dessert_folder, label='Best Cake', do_save=False, do_notify=False, ) best_cake_thread.description = 'Which is the best cake?' self._session.add(best_cake_thread) apple_pie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label='Apple_Pie', do_save=False, do_notify=False, ) apple_pie_recipe.file_extension = '.txt' apple_pie_recipe.depot_file = FileIntent( b'Apple pie Recipe', 'apple_Pie.txt', 'text/plain', ) self._session.add(apple_pie_recipe) Brownie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label='Brownie Recipe', do_save=False, do_notify=False, ) Brownie_recipe.file_extension = '.html' Brownie_recipe.depot_file = FileIntent( b'<p>Brownie Recipe</p>', 'brownie_recipe.html', 'text/html', ) self._session.add(Brownie_recipe) fruits_desserts_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label='Fruits Desserts', parent=dessert_folder, do_save=True, ) menu_page = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=business_workspace, parent=menu_workspace, label='Current Menu', do_save=True, ) new_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label='Bad Fruit Salad', do_save=True, do_notify=False, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) # File at the root for test new_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='New Fruit Salad', do_save=True, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=old_fruit_salad, ): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label='Bad Fruit Salad', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=bad_fruit_salad, ): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad) content_api.create_comment( parent=best_cake_thread, content='<p>What is for you the best cake ever? </br> I personnally vote for Chocolate cupcake!</p>', # nopep8 do_save=True, ) bob_content_api.create_comment( parent=best_cake_thread, content='<p>What about Apple Pie? There are Awesome!</p>', do_save=True, ) reader_content_api.create_comment( parent=best_cake_thread, content='<p>You are right, but Kouign-amann are clearly better.</p>', do_save=True, ) with new_revision( session=self._session, tm=transaction.manager, content=best_cake_thread, ): bob_content_api.update_content( item=best_cake_thread, new_content='What is the best cake?', new_label='Best Cakes?', ) bob_content_api.save(best_cake_thread) with new_revision( session=self._session, tm=transaction.manager, content=tiramisu_page, ): bob_content_api.update_content( item=tiramisu_page, new_content='<p>To cook a great Tiramisu, you need many ingredients.</p>', # nopep8 new_label='Tiramisu Recipe', ) bob_content_api.save(tiramisu_page) self._session.flush()
class ContentOnlyContainer(WebdavContainer): """ Container that can get children content """ def __init__( self, path: str, environ: dict, label: str, content: Content, provider: "TracimDavProvider", workspace: Workspace, tracim_context: "WebdavTracimContext", ) -> None: """ Some rules: - if content given is None, return workspace root contents - if the given content is correct, return the subcontent of this content and user-known workspaces without any user-known parent to the list. - in case of content collision, only the first named content (sorted by content_id from lower to higher) will be returned. """ self.path = path self.environ = environ self.workspace = workspace self.content = content self.tracim_context = tracim_context self.user = tracim_context.current_user self.session = tracim_context.dbsession self.label = label self.provider = provider self.content_api = ContentApi( current_user=self.user, session=tracim_context.dbsession, config=tracim_context.app_config, show_temporary=True, namespaces_filter=[ContentNamespaces.CONTENT], ) # Internal methods def _get_members( self, already_existing_names: typing.Optional[typing.List[str]] = None ) -> typing.List[Content]: members_names = [] members = [] if self.content: parent_id = self.content.content_id children = self.content_api.get_all( content_type=content_type_list.Any_SLUG, workspace=self.workspace, parent_ids=[parent_id], order_by_properties=["content_id"], ) else: children = self.content_api.get_all( content_type=content_type_list.Any_SLUG, workspace=self.workspace, parent_ids=[0], order_by_properties=["content_id"], ) for child in children: if child.file_name in members_names: continue else: members_names.append(child.file_name) members.append(child) return members def _generate_child_content_resource( self, parent_path: str, child_content: Content) -> _DAVResource: content_path = "%s/%s" % ( self.path, webdav_convert_file_name_to_display(child_content.file_name), ) return get_content_resource( path=content_path, environ=self.environ, workspace=self.workspace, content=child_content, tracim_context=self.tracim_context, ) # Container methods def createEmptyResource(self, file_name: str): """ Create a new file on the current workspace/folder. """ content = None fixed_file_name = webdav_convert_file_name_to_display(file_name) path = os.path.join(self.path, file_name) resource = self.provider.getResourceInst(path, self.environ) if resource: content = resource.content try: self.content_api.check_upload_size( int(self.environ["CONTENT_LENGTH"]), self.workspace) except ( FileSizeOverMaxLimitation, FileSizeOverWorkspaceEmptySpace, FileSizeOverOwnerEmptySpace, ) as exc: raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc)) # return item return FakeFileStream( session=self.session, file_name=fixed_file_name, content_api=self.content_api, workspace=self.workspace, content=content, parent=self.content, path=self.path + "/" + fixed_file_name, ) def createCollection(self, label: str) -> "FolderResource": """ Create a new folder for the current workspace/folder. 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. """ folder_label = webdav_convert_file_name_to_bdd(label) try: folder = self.content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, label=folder_label, parent=self.content, ) self.content_api.execute_created_content_actions(folder) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc self.content_api.save(folder) transaction.commit() # fixed_path folder_path = "%s/%s" % (self.path, webdav_convert_file_name_to_display(label)) # return item return FolderResource( folder_path, self.environ, content=folder, tracim_context=self.tracim_context, workspace=self.workspace, ) def getMemberNames(self) -> [str]: """ Access to the list of content names for current workspace/folder """ # INFO - G.M - 2020-14-10 - Unclear if this method is really used by wsgidav retlist = [] for content in self._get_members(): retlist.append( webdav_convert_file_name_to_display(content.file_name)) return retlist def getMember(self, label: str) -> _DAVResource: """ Access to a specific members """ return self.provider.getResourceInst( "%s/%s" % (self.path, webdav_convert_file_name_to_display(label)), self.environ) def getMemberList(self) -> [_DAVResource]: """ Access to the list of content of current workspace/folder """ members = [] for content in self._get_members(): members.append( self._generate_child_content_resource(parent_path=self.path, child_content=content)) return members
class TestFolderMove(FunctionalTest): def setUp(self): super().setUp() self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) self.dbsession = get_tm_session(self.session_factory, transaction.manager) self.admin = self.dbsession.query(User).filter( User.email == "*****@*****.**").one() self.workspace_api = WorkspaceApi(current_user=self.admin, session=self.dbsession, config=self.app_config) self.content_api = ContentApi(current_user=self.admin, session=self.dbsession, config=self.app_config) self.workspace = self.workspace_api.create_workspace(label="test", save_now=True) transaction.commit() def test_api__move_folder_into_itself__err_400(self) -> None: moved_folder = self.content_api.create( label="test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, do_save=True, do_notify=False, ) transaction.commit() move_url = "/api/v2/workspaces/{}/contents/{}/move".format( self.workspace.workspace_id, moved_folder.content_id) body = { "new_parent_id": moved_folder.content_id, "new_workspace_id": self.workspace.workspace_id, } response = self.testapp.put_json(move_url, params=body, status=400) assert response.json_body[ "code"] == ErrorCode.CONFLICTING_MOVE_IN_ITSELF def test_api__move_folder_in_a_direct_child__err_400(self) -> None: moved_folder = self.content_api.create( label="test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, do_save=True, do_notify=False, ) child_folder = self.content_api.create( label="test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, parent=moved_folder, do_save=True, do_notify=False, ) transaction.commit() move_url = "/api/v2/workspaces/{}/contents/{}/move".format( self.workspace.workspace_id, moved_folder.content_id) body = { "new_parent_id": child_folder.content_id, "new_workspace_id": self.workspace.workspace_id, } response = self.testapp.put_json(move_url, params=body, status=400) assert response.json_body[ "code"] == ErrorCode.CONFLICTING_MOVE_IN_CHILD def test_api__move_folder_in_a_sub_child__err_400(self) -> None: moved_folder = self.content_api.create( label="test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, do_save=True, do_notify=False, ) child_folder = self.content_api.create( label="child_test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, parent=moved_folder, do_save=True, do_notify=False, ) sub_child_folder = self.content_api.create( label="sub_child_test_folder", content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, parent=child_folder, do_save=True, do_notify=False, ) transaction.commit() move_url = "/api/v2/workspaces/{}/contents/{}/move".format( self.workspace.workspace_id, moved_folder.content_id) body = { "new_parent_id": sub_child_folder.content_id, "new_workspace_id": self.workspace.workspace_id, } response = self.testapp.put_json(move_url, params=body, status=400) assert response.json_body[ "code"] == ErrorCode.CONFLICTING_MOVE_IN_CHILD
def test_api___elasticsearch_search_ok__filter_by_content_type( self) -> None: dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter( User.email == "*****@*****.**").one() uapi = UserApi(current_user=admin, session=dbsession, config=self.app_config) gapi = GroupApi(current_user=admin, session=dbsession, config=self.app_config) groups = [gapi.get_one_with_name("trusted-users")] user = uapi.create_user( "*****@*****.**", password="******", do_save=True, do_notify=False, groups=groups, ) workspace_api = WorkspaceApi(current_user=admin, session=dbsession, config=self.app_config, show_deleted=True) workspace = workspace_api.create_workspace("test", save_now=True) rapi = RoleApi(current_user=admin, session=dbsession, config=self.app_config) rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False) api = ContentApi(session=dbsession, current_user=user, config=self.app_config) doc = api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch doc", do_save=True, ) api.execute_created_content_actions(doc) doc2 = api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch doc 2", do_save=True, ) api.execute_created_content_actions(doc2) thread = api.create( content_type_slug="thread", workspace=workspace, label="stringtosearch thread", do_save=True, ) api.execute_created_content_actions(thread) folder = api.create( content_type_slug="folder", workspace=workspace, label="stringtosearch folder", do_save=True, ) api.execute_created_content_actions(folder) transaction.commit() self.refresh_elasticsearch() # get all params = {"search_string": "stringtosearch"} self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 4 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 4 params = { "search_string": "stringtosearch", "content_types": "html-document" } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 2 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 2 labels = [content["label"] for content in search_result["contents"]] assert "stringtosearch doc 2" in labels assert "stringtosearch doc" in labels params = { "search_string": "stringtosearch", "content_types": "html-document,thread" } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 3 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 3 labels = [content["label"] for content in search_result["contents"]] assert "stringtosearch doc 2" in labels assert "stringtosearch doc" in labels assert "stringtosearch doc 2" in labels assert "stringtosearch thread" in labels params = {"search_string": "stringtosearch", "content_types": "folder"} self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 1 assert search_result["is_total_hits_accurate"] is True assert len(search_result["contents"]) == 1 assert search_result["contents"][0]["label"] == "stringtosearch folder"
def insert(self): admin = self._session.query(User).filter(User.email == "*****@*****.**").one() bob = self._session.query(User).filter(User.email == "*****@*****.**").one() john_the_reader = ( self._session.query(User).filter(User.email == "*****@*****.**").one() ) admin_workspace_api = WorkspaceApi( current_user=admin, session=self._session, config=self._config ) bob_workspace_api = WorkspaceApi( current_user=bob, session=self._session, config=self._config ) content_api = ContentApi(current_user=admin, session=self._session, config=self._config) bob_content_api = ContentApi(current_user=bob, session=self._session, config=self._config) reader_content_api = ContentApi( current_user=john_the_reader, session=self._session, config=self._config ) role_api = RoleApi(current_user=admin, session=self._session, config=self._config) # Workspaces business_workspace = admin_workspace_api.create_workspace( "Business", description="All importants documents", save_now=True ) recipe_workspace = admin_workspace_api.create_workspace( "Recipes", description="Our best recipes", save_now=True ) other_workspace = bob_workspace_api.create_workspace( "Others", description="Other Workspace", save_now=True ) # Workspaces roles role_api.create_one( user=bob, workspace=recipe_workspace, role_level=UserRoleInWorkspace.CONTENT_MANAGER, with_notif=False, ) role_api.create_one( user=john_the_reader, workspace=recipe_workspace, role_level=UserRoleInWorkspace.READER, with_notif=False, ) # Folders content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label="Tools", do_save=True, do_notify=False, ) menu_workspace = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=business_workspace, label="Menus", do_save=True, do_notify=False, ) dessert_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label="Desserts", do_save=True, do_notify=False, ) content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label="Salads", do_save=True, do_notify=False, ) content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=other_workspace, label="Infos", do_save=True, do_notify=False, ) # Pages, threads, .. tiramisu_page = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=dessert_folder, label="Tiramisu Recipes!!!", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=tiramisu_page): content_api.update_content( item=tiramisu_page, new_content="<p>To cook a greet Tiramisu, you need many ingredients.</p>", new_label="Tiramisu Recipes!!!", ) content_api.save(tiramisu_page, do_notify=False) best_cake_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=recipe_workspace, parent=dessert_folder, label="Best Cake", do_save=False, do_notify=False, ) best_cake_thread.description = "Which is the best cake?" self._session.add(best_cake_thread) apple_pie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label="Apple_Pie", do_save=False, do_notify=False, ) apple_pie_recipe.file_extension = ".txt" apple_pie_recipe.depot_file = FileIntent(b"Apple pie Recipe", "apple_Pie.txt", "text/plain") self._session.add(apple_pie_recipe) brownie_recipe = content_api.create( content_type_slug=content_type_list.File.slug, workspace=recipe_workspace, parent=dessert_folder, label="Brownie Recipe", do_save=False, do_notify=False, ) brownie_recipe.file_extension = ".html" brownie_recipe.depot_file = FileIntent( b"<p>Brownie Recipe</p>", "brownie_recipe.html", "text/html" ) self._session.add(brownie_recipe) fruits_desserts_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=recipe_workspace, label="Fruits Desserts", parent=dessert_folder, do_save=True, do_notify=False, ) content_api.create( content_type_slug=content_type_list.Page.slug, workspace=business_workspace, parent=menu_workspace, label="Current Menu", do_save=True, do_notify=False, ) content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label="New Fruit Salad", do_save=True, do_notify=False, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label="Fruit Salad", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=old_fruit_salad): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad, do_notify=False) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=recipe_workspace, parent=fruits_desserts_folder, label="Bad Fruit Salad", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=bad_fruit_salad): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad, do_notify=False) # File at the root for test content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label="New Fruit Salad", do_save=True, do_notify=False, ) old_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label="Fruit Salad", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=old_fruit_salad): content_api.archive(old_fruit_salad) content_api.save(old_fruit_salad, do_notify=False) bad_fruit_salad = content_api.create( content_type_slug=content_type_list.Page.slug, workspace=other_workspace, label="Bad Fruit Salad", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=bad_fruit_salad): content_api.delete(bad_fruit_salad) content_api.save(bad_fruit_salad, do_notify=False) content_api.create_comment( parent=best_cake_thread, content="<p>What is for you the best cake ever? <br/> I personnally vote for Chocolate cupcake!</p>", do_save=True, do_notify=False, ) bob_content_api.create_comment( parent=best_cake_thread, content="<p>What about Apple Pie? There are Awesome!</p>", do_save=True, do_notify=False, ) reader_content_api.create_comment( parent=best_cake_thread, content="<p>You are right, but Kouign-amann are clearly better.</p>", do_save=True, do_notify=False, ) with new_revision(session=self._session, tm=transaction.manager, content=best_cake_thread): bob_content_api.update_content( item=best_cake_thread, new_content="What is the best cake?", new_label="Best Cakes?" ) bob_content_api.save(best_cake_thread, do_notify=False) with new_revision(session=self._session, tm=transaction.manager, content=tiramisu_page): bob_content_api.update_content( item=tiramisu_page, new_content="<p>To cook a great Tiramisu, you need many ingredients.</p>", new_label="Tiramisu Recipe", ) bob_content_api.save(tiramisu_page, do_notify=False) self._session.flush()
class WorkspaceResource(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, label: str, path: str, environ: dict, workspace: Workspace, tracim_context: "WebdavTracimContext", ) -> None: super(WorkspaceResource, self).__init__(path, environ) self.workspace = workspace self.content = None self.tracim_context = tracim_context self.user = tracim_context.current_user self.session = tracim_context.dbsession self.label = label self.content_api = ContentApi( current_user=self.user, session=tracim_context.dbsession, config=tracim_context.app_config, show_temporary=True, namespaces_filter=[ContentNamespaces.CONTENT], ) 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 webdav_convert_file_name_to_display(self.label) def getDisplayInfo(self): return {"type": "workspace".capitalize()} def getLastModified(self) -> float: return mktime(self.workspace.updated.timetuple()) def getMemberNames(self) -> [str]: retlist = [] children = self.content_api.get_all( parent_ids=[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 != content_type_list.Folder.slug: self._file_count += 1 retlist.append( webdav_convert_file_name_to_display(content.file_name)) return retlist def getMember(self, content_label: str) -> _DAVResource: return self.provider.getResourceInst( "%s/%s" % (self.path, webdav_convert_file_name_to_display(content_label)), self.environ) @webdav_check_right(is_contributor) 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 # fixed path fixed_file_name = webdav_convert_file_name_to_display(file_name) path = os.path.join(self.path, file_name) resource = self.provider.getResourceInst(path, self.environ) if resource: content = resource.content try: self.content_api.check_upload_size( int(self.environ["CONTENT_LENGTH"]), self.workspace) except ( FileSizeOverMaxLimitation, FileSizeOverWorkspaceEmptySpace, FileSizeOverOwnerEmptySpace, ) as exc: raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc)) # return item return FakeFileStream( session=self.session, file_name=fixed_file_name, content_api=self.content_api, workspace=self.workspace, content=content, parent=self.content, path=self.path + "/" + fixed_file_name, ) @webdav_check_right(is_content_manager) def createCollection(self, label: str) -> "FolderResource": """ 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_label = webdav_convert_file_name_to_bdd(label) try: folder = self.content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, label=folder_label, parent=self.content, ) self.content_api.execute_created_content_actions(folder) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc self.content_api.save(folder) transaction.commit() # fixed_path folder_path = "%s/%s" % (self.path, webdav_convert_file_name_to_display(label)) # return item return FolderResource( folder_path, self.environ, content=folder, tracim_context=self.tracim_context, workspace=self.workspace, ) def delete(self): """For now, it is not possible to delete a workspace through the webdav client.""" # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace # of tracim_context is here invalid. self.tracim_context._current_workspace = self.workspace try: can_delete_workspace.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) raise DAVError(HTTP_FORBIDDEN, "Workspace deletion is not allowed through webdav") def supportRecursiveMove(self, destpath): return True def moveRecursive(self, destpath): # INFO - G.M - 2018-12-11 - We only allow renaming if dirname(normpath( destpath)) == self.environ["http_authenticator.realm"]: # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace # of tracim_context is here invalid. self.tracim_context._current_workspace = self.workspace try: can_modify_workspace.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) try: workspace_api = WorkspaceApi(current_user=self.user, session=self.session, config=self.provider.app_config) workspace_api.update_workspace( workspace=self.workspace, label=webdav_convert_file_name_to_bdd( basename(normpath(destpath))), description=self.workspace.description, ) self.session.add(self.workspace) self.session.flush() workspace_api.execute_update_workspace_actions(self.workspace) transaction.commit() except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) def getMemberList(self) -> [_DAVResource]: members = [] children = self.content_api.get_all(False, content_type_list.Any_SLUG, self.workspace) for content in children: content_path = "%s/%s" % ( self.path, webdav_convert_file_name_to_display(content.file_name), ) if content.type == content_type_list.Folder.slug: members.append( FolderResource( path=content_path, environ=self.environ, workspace=self.workspace, content=content, tracim_context=self.tracim_context, )) elif content.type == content_type_list.File.slug: self._file_count += 1 members.append( FileResource( path=content_path, environ=self.environ, content=content, tracim_context=self.tracim_context, )) else: self._file_count += 1 members.append( OtherFileResource(content_path, self.environ, content, tracim_context=self.tracim_context)) return members
def test_api__post_content_comment__err_400__content_not_editable(self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User) \ .filter(User.email == '*****@*****.**') \ .one() # type: User workspace_api = WorkspaceApi( current_user=admin, session=dbsession, config=self.app_config ) business_workspace = workspace_api.get_one(1) content_api = ContentApi( current_user=admin, session=dbsession, config=self.app_config ) tool_folder = content_api.get_one(1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label='Test Thread', do_save=True, do_notify=False, ) with new_revision( session=dbsession, tm=transaction.manager, content=test_thread, ): content_api.update_content( test_thread, new_label='test_thread_updated', new_content='Just a test' ) content_api.set_status(test_thread, 'closed-deprecated') transaction.commit() self.testapp.authorization = ( 'Basic', ( '*****@*****.**', '*****@*****.**' ) ) params = { 'raw_content': 'I strongly disagree, Tiramisu win!' } res = self.testapp.post_json( '/api/v2/workspaces/{}/contents/{}/comments'.format( business_workspace.workspace_id, test_thread.content_id ), params=params, status=400 ) assert res.json_body assert 'code' in res.json_body assert res.json_body['code'] == error.CONTENT_IN_NOT_EDITABLE_STATE
def test_api__post_content_comment__ok_200__nominal_case(self) -> None: """ Get alls comments of a content """ dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User) \ .filter(User.email == '*****@*****.**') \ .one() # type: User workspace_api = WorkspaceApi( current_user=admin, session=dbsession, config=self.app_config ) business_workspace = workspace_api.get_one(1) content_api = ContentApi( current_user=admin, session=dbsession, config=self.app_config ) tool_folder = content_api.get_one(1, content_type=content_type_list.Any_SLUG) test_thread = content_api.create( content_type_slug=content_type_list.Thread.slug, workspace=business_workspace, parent=tool_folder, label='Test Thread', do_save=True, do_notify=False, ) with new_revision( session=dbsession, tm=transaction.manager, content=test_thread, ): content_api.update_content( test_thread, new_label='test_thread_updated', new_content='Just a test' ) transaction.commit() self.testapp.authorization = ( 'Basic', ( '*****@*****.**', '*****@*****.**' ) ) params = { 'raw_content': 'I strongly disagree, Tiramisu win!' } res = self.testapp.post_json( '/api/v2/workspaces/{}/contents/{}/comments'.format( business_workspace.workspace_id, test_thread.content_id ), params=params, status=200 ) comment = res.json_body assert comment['content_id'] assert comment['parent_id'] == test_thread.content_id assert comment['raw_content'] == 'I strongly disagree, Tiramisu win!' assert comment['author'] assert comment['author']['user_id'] == admin.user_id # TODO - G.M - 2018-06-172 - [avatar] setup avatar url assert comment['author']['avatar_url'] is None assert comment['author']['public_name'] == admin.display_name # TODO - G.M - 2018-06-179 - better check for datetime assert comment['created']
def test_api___simple_search_ok__filter_by_deleted_archived_active(self) -> None: dbsession = get_tm_session(self.session_factory, transaction.manager) admin = dbsession.query(User).filter(User.email == "*****@*****.**").one() uapi = UserApi(current_user=admin, session=dbsession, config=self.app_config) gapi = GroupApi(current_user=admin, session=dbsession, config=self.app_config) groups = [gapi.get_one_with_name("trusted-users")] user = uapi.create_user( "*****@*****.**", password="******", do_save=True, do_notify=False, groups=groups, ) workspace_api = WorkspaceApi( current_user=admin, session=dbsession, config=self.app_config, show_deleted=True ) workspace = workspace_api.create_workspace("test", save_now=True) rapi = RoleApi(current_user=admin, session=dbsession, config=self.app_config) rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False) api = ContentApi(session=dbsession, current_user=user, config=self.app_config) api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch active", do_save=True, ) api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch active 2", do_save=True, ) deleted_content = api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch deleted", do_save=True, ) with new_revision(session=dbsession, tm=transaction.manager, content=deleted_content): api.delete(deleted_content) api.save(deleted_content) archived_content = api.create( content_type_slug="html-document", workspace=workspace, label="stringtosearch archived", do_save=True, ) with new_revision(session=dbsession, tm=transaction.manager, content=archived_content): api.archive(archived_content) api.save(archived_content) transaction.commit() # get all params = { "search_string": "stringtosearch", "show_deleted": 1, "show_archived": 1, "show_active": 1, } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 4 assert search_result["is_total_hits_accurate"] is False assert len(search_result["contents"]) == 4 # get only active params = {"search_string": "stringtosearch"} self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) default_search_result = res.json_body assert default_search_result assert default_search_result["total_hits"] == 2 assert default_search_result["is_total_hits_accurate"] is False assert len(default_search_result["contents"]) == 2 assert default_search_result["contents"][0]["label"] == "stringtosearch active 2" assert default_search_result["contents"][1]["label"] == "stringtosearch active" params = { "search_string": "stringtosearch", "show_active": 1, "show_deleted": 0, "show_archived": 0, } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) only_active_search_result = res.json_body assert only_active_search_result == default_search_result params = { "search_string": "stringtosearch", "show_active": 1, "show_deleted": 1, "show_archived": 0, } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 3 assert search_result["is_total_hits_accurate"] is False assert len(search_result["contents"]) == 3 assert search_result["contents"][0]["label"].startswith("stringtosearch deleted") assert search_result["contents"][1]["label"] == "stringtosearch active 2" assert search_result["contents"][2]["label"] == "stringtosearch active" params = { "search_string": "stringtosearch", "show_active": 0, "show_deleted": 0, "show_archived": 1, } self.testapp.authorization = ("Basic", ("*****@*****.**", "*****@*****.**")) res = self.testapp.get("/api/v2/search/content".format(), status=200, params=params) search_result = res.json_body assert search_result assert search_result["total_hits"] == 1 assert search_result["is_total_hits_accurate"] is False assert len(search_result["contents"]) == 1 assert search_result["contents"][0]["label"].startswith("stringtosearch archived")
def upload_files( self, upload_permission: UploadPermission, uploader_username: str, message: typing.Optional[str], files: typing.List[cgi.FieldStorage], do_notify: bool = False, ) -> typing.List[ContentInContext]: content_api = ContentApi( config=self._config, current_user=upload_permission.author, session=self._session, namespaces_filter=[ContentNamespaces.UPLOAD], ) translator = Translator(app_config=self._config) _ = translator.get_translation current_datetime = datetime.utcnow() folder_label = _( "Files uploaded by {username} on {date} at {time}").format( username=uploader_username, date=format_date(current_datetime, locale=translator.default_lang), time=format_time(current_datetime, locale=translator.default_lang), ) try: upload_folder = content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=upload_permission.workspace, label=folder_label, do_notify=False, do_save=True, content_namespace=ContentNamespaces.UPLOAD, ) except ContentFilenameAlreadyUsedInFolder: upload_folder = content_api.get_one_by_filename( filename=folder_label, workspace=upload_permission.workspace) created_contents = [] if message: comment_message = _("Message from {username}: {message}").format( username=uploader_username, message=message) else: comment_message = _("Uploaded by {username}.").format( username=uploader_username) for _file in files: content = content_api.create( filename=_file.filename, content_type_slug=content_type_list.File.slug, workspace=upload_permission.workspace, parent=upload_folder, do_notify=False, content_namespace=ContentNamespaces.UPLOAD, ) content_api.save(content, ActionDescription.CREATION) with new_revision(session=self._session, tm=transaction.manager, content=content): content_api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) content_api.create_comment(parent=content, content=comment_message, do_save=True, do_notify=False) created_contents.append( content_api.get_content_in_context(content)) content_api.execute_created_content_actions(content) if do_notify: workspace_lib = WorkspaceApi(config=self._config, current_user=upload_permission.author, session=self._session) self._notify_uploaded_contents( uploader_username=uploader_username, workspace_in_context=workspace_lib.get_workspace_with_context( upload_permission.workspace), uploader_message=message, uploaded_contents=created_contents, uploader_email=upload_permission.email, ) return created_contents
class WorkspaceResource(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: Workspace, tracim_context: 'WebdavTracimContext' ) -> None: super(WorkspaceResource, self).__init__(path, environ) self.workspace = workspace self.content = None self.tracim_context = tracim_context self.user = tracim_context.current_user self.session = tracim_context.dbsession self.content_api = ContentApi( current_user=self.user, session=tracim_context.dbsession, config=tracim_context.app_config, 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 webdav_convert_file_name_to_display(self.workspace.label) def getDisplayInfo(self): return { 'type': "workspace".capitalize(), } def getLastModified(self) -> float: return mktime(self.workspace.updated.timetuple()) def getMemberNames(self) -> [str]: retlist = [] children = self.content_api.get_all( parent_ids=[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 != content_type_list.Folder.slug: self._file_count += 1 retlist.append(webdav_convert_file_name_to_display(content.file_name)) return retlist def getMember(self, content_label: str) -> _DAVResource: return self.provider.getResourceInst( '%s/%s' % (self.path, webdav_convert_file_name_to_display(content_label)), self.environ ) @webdav_check_right(is_contributor) 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 # fixed path fixed_file_name = webdav_convert_file_name_to_display(file_name) path = os.path.join(self.path, file_name) resource = self.provider.getResourceInst(path, self.environ) if resource: content = resource.content # return item return FakeFileStream( session=self.session, file_name=fixed_file_name, content_api=self.content_api, workspace=self.workspace, content=content, parent=self.content, path=self.path + '/' + fixed_file_name ) @webdav_check_right(is_content_manager) def createCollection(self, label: str) -> 'FolderResource': """ 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_label = webdav_convert_file_name_to_bdd(label) try: folder = self.content_api.create( content_type_slug=content_type_list.Folder.slug, workspace=self.workspace, label=folder_label, parent=self.content ) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) from exc self.content_api.save(folder) transaction.commit() # fixed_path folder_path = '%s/%s' % (self.path, webdav_convert_file_name_to_display(label)) # return item return FolderResource( folder_path, self.environ, content=folder, tracim_context=self.tracim_context, workspace=self.workspace, ) def delete(self): """For now, it is not possible to delete a workspace through the webdav client.""" # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace # of tracim_context is here invalid. self.tracim_context._current_workspace = self.workspace try: can_delete_workspace.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) raise DAVError(HTTP_FORBIDDEN) def supportRecursiveMove(self, destpath): return True def moveRecursive(self, destpath): # INFO - G.M - 2018-12-11 - We only allow renaming if dirname(normpath(destpath)) == self.environ['http_authenticator.realm']: # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace # of tracim_context is here invalid. self.tracim_context._current_workspace = self.workspace try: can_modify_workspace.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) try: self.workspace.label = webdav_convert_file_name_to_bdd(basename(normpath(destpath))) self.session.add(self.workspace) self.session.flush() transaction.commit() except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) def getMemberList(self) -> [_DAVResource]: members = [] children = self.content_api.get_all(False, content_type_list.Any_SLUG, self.workspace) for content in children: content_path = '%s/%s' % (self.path, webdav_convert_file_name_to_display(content.file_name)) if content.type == content_type_list.Folder.slug: members.append( FolderResource( path=content_path, environ=self.environ, workspace=self.workspace, content=content, tracim_context=self.tracim_context ) ) elif content.type == content_type_list.File.slug: self._file_count += 1 members.append( FileResource( path=content_path, environ=self.environ, content=content, tracim_context=self.tracim_context ) ) else: self._file_count += 1 members.append( OtherFileResource( content_path, self.environ, content, tracim_context=self.tracim_context )) return members