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 search_content( self, search_string: str, size: typing.Optional[int] = SEARCH_DEFAULT_RESULT_NB, page_nb: typing.Optional[int] = 1, content_types: typing.Optional[typing.List[str]] = None, show_deleted: bool = False, show_archived: bool = False, show_active: bool = True, ) -> ContentSearchResponse: """ Search content with sql - do no show archived/deleted content by default - filter content found according to workspace of current_user """ if not search_string: return EmptyContentSearchResponse() content_api = ContentApi( session=self._session, current_user=self._user, config=self._config, show_deleted=show_deleted, show_archived=show_archived, show_active=show_active, ) keywords = self.get_keywords(search_string) offset = self.offset_from_pagination(size, page_nb) return self.search( keywords=keywords, size=size, offset=offset, content_types=content_types, content_api=content_api, )
def update_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ update thread """ 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, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.save(content) return api.get_content_in_context(content)
def download_file(self, context, request: TracimRequest, hapic_data=None): """ Download raw file of last revision of content. Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) file = DepotManager.get().get(content.depot_file) filename = hapic_data.path.filename if not filename or filename == 'raw': filename = content.file_name return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=hapic_data.query.force_download )
def get_file_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get file revisions """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) revisions = content.revisions return [ api.get_revision_in_context(revision) for revision in revisions ]
def delete_comment(self, context, request: TracimRequest, hapic_data=None): """ Delete comment """ 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, ) wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) parent = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG, workspace=workspace) comment = api.get_one( hapic_data.path.comment_id, content_type=content_type_list.Comment.slug, workspace=workspace, parent=parent, ) with new_revision(session=request.dbsession, tm=transaction.manager, content=comment): api.delete(comment) return
def __init__( self, path: str, environ: dict, workspace: Workspace, content: Content, tracim_context: "WebdavTracimContext", ): DAVCollection.__init__(self, path, environ) self.content_container = ContentOnlyContainer( path, environ, provider=self.provider, content=content, label=workspace.filemanager_filename, workspace=workspace, tracim_context=tracim_context, ) self.tracim_context = tracim_context self.content_api = ContentApi( current_user=tracim_context.current_user, session=tracim_context.dbsession, config=tracim_context.app_config, show_temporary=True, namespaces_filter=[ContentNamespaces.CONTENT], ) self.content = content self.session = tracim_context.dbsession
def preview_jpg(self, context, request: TracimRequest, hapic_data=None): """ Obtain normally sized jpg preview of last revision of content. Good pratice for filename is `filename is {label}_page_{page_number}.jpg`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) allowed_dim = api.get_jpg_preview_allowed_dim() jpg_preview_path = api.get_jpg_preview_path( content_id=content.content_id, revision_id=content.revision_id, page_number=hapic_data.query.page, file_extension=content.file_extension, width=allowed_dim.dimensions[0].width, height=allowed_dim.dimensions[0].height, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}.jpg".format( label=content.label, page_number=hapic_data.query.page ) return HapicFile( file_path=jpg_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, )
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 download_file(self, context, request: TracimRequest, hapic_data=None): """ Download raw file of last revision of content. Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) try: file = DepotManager.get().get(content.depot_file) except IOError as exc: raise TracimFileNotFound( "file related to revision {} of content {} not found in depot.".format( content.revision_id, content.content_id ) ) from exc filename = hapic_data.path.filename if not filename or filename == "raw": filename = content.file_name return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=hapic_data.query.force_download, content_length=file.content_length, last_modified=content.updated, )
def preview_pdf_revision(self, context, request: TracimRequest, hapic_data=None): """ Obtain a specific page pdf preview of a specific revision of content. Good pratice for filename is filename is `{label}_page_{page_number}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content) pdf_preview_path = api.get_pdf_preview_path( revision.content_id, revision.revision_id, page_number=hapic_data.query.page, file_extension=revision.file_extension, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}.pdf".format( label=content.label, page_number=hapic_data.query.page ) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, )
def last_active_content(self, context, request: TracimRequest, hapic_data=None): """ Get last_active_content for user """ app_config = request.registry.settings["CFG"] # type: CFG content_filter = hapic_data.query api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) before_content = None if content_filter.before_content_id: before_content = api.get_one( content_id=content_filter.before_content_id, workspace=workspace, content_type=content_type_list.Any_SLUG, ) last_actives = api.get_last_active(workspace=workspace, limit=content_filter.limit or None, before_content=before_content) return [ api.get_content_in_context(content) for content in last_actives ]
def update_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8 """ update folder """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.set_allowed_content( content=content, allowed_content_type_slug_list=hapic_data.body.sub_content_types # nopep8 ) api.save(content) return api.get_content_in_context(content)
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 unarchive_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Restore a content from archive. The content will be visible and editable again. """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_archived=True, show_deleted=True, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.unarchive(content) return
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 _create_content_event(self, operation: OperationType, content: Content, context: TracimContext) -> None: current_user = context.safe_current_user() content_api = ContentApi(context.dbsession, current_user, self._config) content_in_context = content_api.get_content_in_context(content) content_schema = EventApi.get_content_schema_for_type(content.type) content_dict = content_schema.dump(content_in_context).data workspace_api = WorkspaceApi(context.dbsession, current_user, self._config, show_deleted=True) workspace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(content_in_context.workspace.workspace_id)) fields = { Event.CONTENT_FIELD: content_dict, Event.WORKSPACE_FIELD: EventApi.workspace_schema.dump(workspace_in_context).data, } event_api = EventApi(current_user, context.dbsession, self._config) event_api.create_event( entity_type=EntityType.CONTENT, operation=operation, additional_fields=fields, entity_subtype=content.type, context=context, )
def contents_read_status(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ get user_read status of contents """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) last_actives = api.get_last_active( workspace=workspace, limit=None, before_content=None, content_ids=hapic_data.query.contents_ids or None) return [ api.get_content_in_context(content) for content in last_actives ]
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], )
def enable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ enable workspace notification """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.candidate_user, workspace) rapi = RoleApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) role = rapi.get_one(request.candidate_user.user_id, workspace.workspace_id) wapi.save(workspace) return
def upload_file(self, context, request: TracimRequest, hapic_data=None): """ Upload a new version of raw file of content. This will create a new revision. Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) _file = hapic_data.files.files 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.save(content) return
def workspace_content( self, context, request: TracimRequest, hapic_data=None, ) -> typing.List[ContentInContext]: """ return a list of contents of the space. This is NOT the full content list: by default, returned contents are the ones at root level. In order to get contents in a given folder, then use parent_id query filter. You can also show.hide archived/deleted contents. """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_archived=content_filter.show_archived, show_deleted=content_filter.show_deleted, show_active=content_filter.show_active, ) contents = api.get_all(parent_id=content_filter.parent_id, workspace=request.current_workspace, content_type=content_filter.content_type or content_type_list.Any_SLUG, label=content_filter.label, order_by_properties=[Content.label]) contents = [ api.get_content_in_context(content) for content in contents ] return contents
def preview_pdf_full(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Obtain a full pdf preview (all page) of last revision of content. Good pratice for filename is filename is `{label}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) pdf_preview_path = api.get_full_pdf_preview_path( content.revision_id, file_extension=content.file_extension, ) filename = hapic_data.path.filename if not filename or filename == 'raw': filename = "{label}.pdf".format(label=content.label) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download )
def get_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Convenient route allowing to get detail about a content without to known routes associated to its content type. This route generate a HTTP 302 with the right url """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(content_id=hapic_data.path['content_id'], content_type=content_type_list.Any_SLUG) content_type = content_type_list.get_one_by_slug(content.type).slug # TODO - G.M - 2018-08-03 - Jsonify redirect response ? raise HTTPFound( "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}". format( base_url=BASE_API_V2, workspace_id=content.workspace_id, content_type=content_type, content_id=content.content_id, ))
def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None: # nopep8 """ set file status """ 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, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.set_status( content, hapic_data.body.status, ) api.save(content) return
def delete_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Move a content to the trash. After that, the content will be invisible by default. This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.delete(content) return
def set_thread_status(self, context, request: TracimRequest, hapic_data=None) -> None: """ set thread status """ 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, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) if content.status == request.json_body.get("status"): raise ContentStatusException( "Content id {} already have status {}".format( content.content_id, content.status)) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.set_status(content, hapic_data.body.status) api.save(content) return
def archive_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Archives a content. The content will be invisible but still available. Difference with delete is that optimizing workspace will not delete archived contents This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. the difference with delete is that optimizing workspace will not delete archived contents """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG) # nopep8 with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.archive(content) return
def guest_download_check(self, context, request: TracimRequest, hapic_data=None) -> None: """ Check if share token is correct and password given valid """ app_config = request.registry.settings["CFG"] # type: CFG api = ShareLib(current_user=None, session=request.dbsession, config=app_config) content_share = api.get_content_share_by_token( share_token=hapic_data.path.share_token) # type: ContentShare # TODO - G.M - 2019-08-01 - verify in access to content share can be granted # we should considered do these check at decorator level api.check_password(content_share, password=hapic_data.body.password) content = ContentApi(current_user=None, session=request.dbsession, config=app_config).get_one( content_share.content_id, content_type=content_type_list.Any_SLUG) workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(content.workspace_id) workspace_api.check_public_download_enabled(workspace) if content.type not in shareables_content_type: raise ContentTypeNotAllowed()
def _get_content(self, content_path_fetcher): path = content_path_fetcher() content_path = self.reduce_path(path) splited_local_path = content_path.strip('/').split('/') workspace_name = webdav_convert_file_name_to_bdd(splited_local_path[0]) wapi = WorkspaceApi( current_user=self.current_user, session=self.dbsession, config=self.app_config, ) workspace = wapi.get_one_by_label(workspace_name) parents = [] if len(splited_local_path) > 2: parent_string = splited_local_path[1:-1] parents = [ webdav_convert_file_name_to_bdd(x) for x in parent_string ] content_api = ContentApi(config=self.app_config, current_user=self.current_user, session=self.dbsession) return content_api.get_one_by_filename_and_parent_labels( content_label=webdav_convert_file_name_to_bdd(basename(path)), content_parent_labels=parents, workspace=workspace, )