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 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 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 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 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 __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 get_html_document_preview( self, context, request: TracimRequest, hapic_data=None ) -> HapicFile: """ Download preview of html document 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) file = BytesIO() byte_size = file.write(content.description.encode("utf-8")) file.seek(0) filename = hapic_data.path.filename # INFO - G.M - 2019-08-08 - use given filename in all case but none or # "raw", where filename returned will be original file one. if not filename or filename == "raw": filename = content.file_name return HapicFile( file_object=file, mimetype=CONTENT_TYPE_TEXT_HTML, filename=filename, as_attachment=hapic_data.query.force_download, content_length=byte_size, last_modified=content.updated, )
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 _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 account_last_active_content(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Get last_active_content for user """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_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 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 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 preview_pdf_full_revision(self, context, request: TracimRequest, hapic_data=None): """ Obtain full pdf preview of a specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}.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_full_pdf_preview_path( revision.revision_id, file_extension=revision.file_extension) filename = hapic_data.path.filename # INFO - G.M - 2019-08-08 - use given filename in all case but none or # "raw", where filename returned will be a custom one. if not filename or filename == "raw": filename = "{label}_r{revision_id}.pdf".format( revision_id=revision.revision_id, label=revision.label) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, )
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, )
def account_contents_read_status(self, context, request: TracimRequest, hapic_data=None): """ get user_read status of contents """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) 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.content_ids or None, ) return [ api.get_content_in_context(content) for content in last_actives ]
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, )
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 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 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 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 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) api.execute_update_content_actions(content) return api.get_content_in_context(content)
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 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 __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 account_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.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_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.content_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 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 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_ids=content_filter.parent_ids, complete_path_to_id=content_filter.complete_path_to_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 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 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 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 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 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 get_html_document_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get html_document 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 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_query(self): content1 = self.test_create() with new_revision(session=self.session, tm=transaction.manager, content=content1): content1.description = "TEST_CONTENT_DESCRIPTION_1_UPDATED" self.session.flush() content2 = self.test_create(key="2") with new_revision(session=self.session, tm=transaction.manager, content=content2): content2.description = "TEST_CONTENT_DESCRIPTION_2_UPDATED" self.session.flush() workspace1 = (self.session.query(Workspace).filter( Workspace.label == "TEST_WORKSPACE_1").one()) workspace2 = (self.session.query(Workspace).filter( Workspace.label == "TEST_WORKSPACE_2").one()) # To get Content in database # we have to join Content and ContentRevisionRO # with particular condition: # Join have to be on most recent revision join_sub_query = (self.session.query( ContentRevisionRO.revision_id ).filter(ContentRevisionRO.content_id == Content.id).order_by( ContentRevisionRO.revision_id.desc()).limit(1).correlate(Content)) base_query = self.session.query(Content).join( ContentRevisionRO, and_( Content.id == ContentRevisionRO.content_id, ContentRevisionRO.revision_id == join_sub_query, ), ) pattern = "TEST_CONTENT_DESCRIPTION_%_UPDATED" eq_(2, base_query.filter(Content.description.like(pattern)).count()) eq_(1, base_query.filter(Content.workspace == workspace1).count()) eq_(1, base_query.filter(Content.workspace == workspace2).count()) content1_from_query = base_query.filter( Content.workspace == workspace1).one() eq_(content1.id, content1_from_query.id) eq_("TEST_CONTENT_DESCRIPTION_1_UPDATED", content1_from_query.description) self.session.query(User).filter( User.email == "*****@*****.**").one() api = ContentApi(current_user=None, session=self.session, config=self.app_config) api.get_one(content1.id, content_type_list.Page.slug, workspace1)
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 content(self) -> ContentInContext: # TODO - G.M - 2019-07-31 - import here to avoid recursive import. from tracim_backend.lib.core.content import ContentApi content_api = ContentApi(config=self.config, session=self.dbsession, current_user=None) content = content_api.get_one(content_id=self.content_share.content_id, content_type=content_type_list.Any_SLUG) return content_api.get_content_in_context(content)
def __init__(self, path: str, environ: dict, content: Content, tracim_context: "WebdavTracimContext") -> None: super(FileResource, self).__init__(path, environ) self.tracim_context = tracim_context self.content = content self.user = tracim_context.current_user self.session = tracim_context.dbsession self.content_api = ContentApi(current_user=self.user, config=tracim_context.app_config, session=self.session)
def exists(self, path, environ) -> bool: """ Called by wsgidav to check if a certain path is linked to a _DAVResource """ tracim_context = environ["tracim_context"] tracim_context.set_path(path) path = normpath(path) working_path = tracim_context.reduce_path(path) root_path = environ["http_authenticator.realm"] parent_path = dirname(working_path) user = tracim_context.current_user session = tracim_context.dbsession if path == root_path: return True try: workspace = tracim_context.current_workspace except WorkspaceNotFound: workspace = None if parent_path == root_path or workspace is None: return workspace is not None # TODO bastien: Arnaud avait mis a True, verif le comportement # lorsque l'on explore les dossiers archive et deleted content_api = ContentApi( current_user=user, session=session, config=self.app_config, show_archived=False, show_deleted=False, namespaces_filter=[ContentNamespaces.CONTENT], ) revision_id = re.search(r"/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) ([^/].+)$", path) is_archived = self.is_path_archive(path) is_deleted = self.is_path_delete(path) if revision_id: revision_id = revision_id.group(1) content = content_api.get_one_revision(revision_id) else: try: content = tracim_context.current_content except ContentNotFound: content = None return ( content is not None and content.is_deleted == is_deleted and content.is_archived == is_archived )
def is_editable(self) -> bool: from tracim_backend.lib.core.content import ContentApi content_api = ContentApi( current_user=self._user, session=self.dbsession, config=self.config, show_deleted=True, show_archived=True, show_active=True, show_temporary=True, ) return content_api.is_editable(self.content)
def exists(self, path, environ) -> bool: """ Called by wsgidav to check if a certain path is linked to a _DAVResource """ tracim_context = environ['tracim_context'] tracim_context.set_path(path) path = normpath(path) working_path = tracim_context.reduce_path(path) root_path = environ['http_authenticator.realm'] parent_path = dirname(working_path) user = tracim_context.current_user session = tracim_context.dbsession if path == root_path: return True try: workspace = tracim_context.current_workspace except WorkspaceNotFound: workspace = None if parent_path == root_path or workspace is None: return workspace is not None # TODO bastien: Arnaud avait mis a True, verif le comportement # lorsque l'on explore les dossiers archive et deleted content_api = ContentApi( current_user=user, session=session, config=self.app_config, show_archived=False, show_deleted=False ) revision_id = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) ([^/].+)$', path) is_archived = self.is_path_archive(path) is_deleted = self.is_path_delete(path) if revision_id: revision_id = revision_id.group(1) content = content_api.get_one_revision(revision_id) else: try: content = tracim_context.current_content except ContentNotFound: content = None return content is not None \ and content.is_deleted == is_deleted \ and content.is_archived == is_archived
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 _get_content(self, content_id_fetcher): content_id = content_id_fetcher() api = ContentApi( current_user = self.current_user, show_deleted = True, show_archived = True, session = self.dbsession, config = self.app_config, ) return api.get_one( content_id=content_id, workspace=self.current_workspace, content_type=content_type_list.Any_SLUG )
def set_account_content_as_unread(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ set user_read status of content to unread """ 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, ) api.mark_unread(request.current_content, do_flush=True) return
def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Get allowed dimensions of jpg preview. If restricted is true, only those dimensions are strictly accepted. """ 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, ) return api.get_jpg_preview_allowed_dim()
def set_account_workspace_as_read(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ set user_read status of all content of workspace to read """ 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, ) api.mark_read__workspace(request.current_workspace) return
def getMemberList(self) -> [_DAVResource]: members = [] content_api = ContentApi( current_user=self.user, config=self.provider.app_config, session=self.session, ) visible_children = content_api.get_all( [self.content.content_id], content_type_list.Any_SLUG, self.workspace, ) for content in visible_children: content_path = '%s/%s' % (self.path, webdav_convert_file_name_to_display(content.file_name)) try: 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( path=content_path, environ=self.environ, content=content, tracim_context=self.tracim_context )) except NotImplementedError as exc: pass return members
def get_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8 """ Get html document content """ 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 ) return api.get_content_in_context(content)
def download_revisions_file(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Download raw file for specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}{file_extension}`. 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 ) revision = api.get_one_revision( revision_id=hapic_data.path.revision_id, content=content ) try: file = DepotManager.get().get(revision.depot_file) except IOError as exc: raise TracimFileNotFound( 'file related to revision {} of content {} not found in depot.'.format( revision.revision_id, revision.content_id ) ) from exc filename = hapic_data.path.filename if not filename or filename == 'raw': filename = "{label}_r{revision_id}{file_extension}".format( label=revision.file_name, revision_id=revision.revision_id, file_extension=revision.file_extension ) return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=hapic_data.query.force_download )
def update_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8 """ update_html_document """ 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.save(content) return api.get_content_in_context(content)
def sized_preview_jpg_revision(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Obtain resized jpg preview of a specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg`. 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 ) revision = api.get_one_revision( revision_id=hapic_data.path.revision_id, content=content ) jpg_preview_path = api.get_jpg_preview_path( content_id=content.content_id, revision_id=revision.revision_id, page_number=hapic_data.query.page, height=hapic_data.path.height, width=hapic_data.path.width, file_extension=revision.file_extension, ) filename = hapic_data.path.filename if not filename or filename == 'raw': filename = "{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg".format( # nopep8 revision_id=revision.revision_id, label=revision.label, page_number=hapic_data.query.page, width=hapic_data.path.width, height=hapic_data.path.height ) return HapicFile( file_path=jpg_preview_path, filename=filename, as_attachment=hapic_data.query.force_download )
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 is_editable(self) -> bool: from tracim_backend.lib.core.content import ContentApi content_api = ContentApi( current_user=self._user, session=self.dbsession, config=self.config, show_deleted=True, show_archived=True, show_active=True, show_temporary=True, ) # INFO - G.M - 2018-11-02 - check if revision is last one and if it is, # return editability of content. content = content_api.get_one( content_id=self.revision.content_id, content_type=content_type_list.Any_SLUG ) if content.revision_id == self.revision_id: return content_api.is_editable(content) # INFO - G.M - 2018-11-02 - old revision are not editable return False
def page_nb(self) -> typing.Optional[int]: """ :return: page_nb of content if available, None if unavailable """ if self.content.depot_file: from tracim_backend.lib.core.content import ContentApi content_api = ContentApi( current_user=self._user, session=self.dbsession, config=self.config, show_deleted=True, show_archived=True, show_active=True, show_temporary=True, ) return content_api.get_preview_page_nb( self.content.revision_id, file_extension=self.content.file_extension ) else: return None
def has_jpeg_preview(self) -> bool: """ :return: bool about if jpeg version of content is available """ if not self.content.depot_file: return False from tracim_backend.lib.core.content import ContentApi content_api = ContentApi( current_user=self._user, session=self.dbsession, config=self.config, show_deleted=True, show_archived=True, show_active=True, show_temporary=True, ) return content_api.has_jpeg_preview( self.content.revision_id, file_extension=self.content.file_extension )
def set_html_document_status( self, context, request: TracimRequest, hapic_data=None ) -> None: """ set html_document 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 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 test_children(self): admin = self.session.query(User).filter( User.email == '*****@*****.**' ).one() self._create_thread_and_test( workspace_name='workspace_1', folder_name='folder_1', thread_name='thread_1', user=admin ) workspace = self.session.query(Workspace).filter( Workspace.label == 'workspace_1' ).one() content_api = ContentApi( session=self.session, current_user=admin, config=self.app_config, ) folder = content_api.get_canonical_query().filter( Content.label == 'folder_1' ).one() eq_([folder, ], list(workspace.get_valid_children()))