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 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 _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 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 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 _create_subscription_event(self, operation: OperationType, subscription: WorkspaceSubscription, context: TracimContext) -> None: current_user = context.safe_current_user() workspace_api = WorkspaceApi( session=context.dbsession, config=self._config, current_user=None, ) workspace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(subscription.workspace_id)) user_api = UserApi(current_user, context.dbsession, self._config, show_deleted=True) subscription_author_in_context = user_api.get_user_with_context( subscription.author) fields = { Event.WORKSPACE_FIELD: EventApi.workspace_schema.dump(workspace_in_context).data, Event.SUBSCRIPTION_FIELD: EventApi.workspace_subscription_schema.dump(subscription).data, Event.USER_FIELD: EventApi.user_schema.dump(subscription_author_in_context).data, } event_api = EventApi(current_user, context.dbsession, self._config) event_api.create_event( entity_type=EntityType.WORKSPACE_SUBSCRIPTION, operation=operation, additional_fields=fields, context=context, )
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 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 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 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 _get_current_workspace( self, user: User, request: 'TracimRequest' ) -> Workspace: """ Get current workspace from request :param user: User who want to check the workspace :param request: pyramid request :return: current workspace """ workspace_id = '' try: if 'workspace_id' in request.matchdict: workspace_id_str = request.matchdict['workspace_id'] if not isinstance(workspace_id_str, str) or not workspace_id_str.isdecimal(): # nopep8 raise InvalidWorkspaceId('workspace_id is not a correct integer') # nopep8 workspace_id = int(request.matchdict['workspace_id']) if not workspace_id: raise WorkspaceNotFoundInTracimRequest('No workspace_id property found in request') # nopep8 wapi = WorkspaceApi( current_user=user, session=request.dbsession, config=request.registry.settings['CFG'], show_deleted=True, ) workspace = wapi.get_one(workspace_id) except NoResultFound as exc: raise WorkspaceNotFound( 'Workspace {} does not exist ' 'or is not visible for this user'.format(workspace_id) ) from exc return workspace
def create_workspace(self, context, request: TracimRequest, hapic_data=None): """ Create a workspace. This route is for trusted users and administrators. """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) parent = None if hapic_data.body.parent_id: parent = wapi.get_one(workspace_id=hapic_data.body.parent_id) workspace = wapi.create_workspace( label=hapic_data.body.label, description=hapic_data.body.description, access_type=hapic_data.body.access_type, save_now=True, agenda_enabled=hapic_data.body.agenda_enabled, public_download_enabled=hapic_data.body.public_download_enabled, public_upload_enabled=hapic_data.body.public_upload_enabled, default_user_role=hapic_data.body.default_user_role, parent=parent, ) wapi.execute_created_workspace_actions(workspace) return wapi.get_workspace_with_context(workspace)
def enable_account_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ enable workspace notification """ app_config = request.registry.settings['CFG'] 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 = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.current_user, workspace) rapi = RoleApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) role = rapi.get_one(request.current_user.user_id, workspace.workspace_id) # nopep8 wapi.save(workspace) return
def _get_workspace(self, workspace_id_fetcher): workspace_id = workspace_id_fetcher() wapi = WorkspaceApi( current_user=self.current_user, session=self.dbsession, config=self.app_config, show_deleted=True, ) return wapi.get_one(workspace_id)
def _get_workspace(self, workspace_id_fetcher): workspace_id = workspace_id_fetcher() wapi = WorkspaceApi( current_user=self.current_user, session=self.dbsession, config=self.app_config, show_deleted=True, ) return wapi.get_one(workspace_id)
def guest_download_file(self, context, request: TracimRequest, hapic_data=None) -> HapicFile: 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 if hapic_data.forms: password = hapic_data.forms.password else: password = None api.check_password(content_share, password=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() try: file = DepotManager.get( app_config.UPLOADED_FILES__STORAGE__STORAGE_NAME).get( content.depot_file) except IOError as exc: raise TracimFileNotFound( "file related to revision {} of content {} not found in depot." .format(content.cached_revision_id, content.content_id)) from exc filename = hapic_data.path.filename # INFO - G.M - 2019-08-08 - use given filename in all case but none or # "raw", when filename returned will be original file one. if not filename or filename == "raw": filename = content.file_name return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=True, content_length=file.content_length, last_modified=content.updated, )
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 disable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ disable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.disable_notifications(request.candidate_user, workspace) wapi.save(workspace)
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__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 guest_upload_file(self, context, request: TracimRequest, hapic_data=None) -> None: """ upload files as guest """ # TODO - G.M - 2019-08-14 - replace UploadFiles Object hack to proper hapic support # see upload_files = UploadFiles(request, prefix_pattern="file_") # INFO - G.M - 2019-09-03 - check validation of file here, because hapic can't # handle them. verify if almost one file as been given. if len(upload_files.files) < 1: raise NoFileValidationError( "No files given at input, validation failed.") app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted # we should considered do these check at decorator level workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) api.check_password(upload_permission, password=hapic_data.forms.password) content_api = ContentApi(current_user=None, session=request.dbsession, config=app_config) content_api.check_workspace_size_limitation( content_length=request.content_length, workspace=workspace) content_api.check_owner_size_limitation( content_length=request.content_length, workspace=workspace) api.upload_files( upload_permission=upload_permission, uploader_username=hapic_data.forms.username, message=hapic_data.forms.message, files=upload_files.files, do_notify=app_config.EMAIL__NOTIFICATION__ACTIVATED, ) return
def _create_role_event(self, operation: OperationType, role: UserRoleInWorkspace, context: TracimContext) -> None: current_user = context.safe_current_user() workspace_api = WorkspaceApi( session=context.dbsession, config=self._config, show_deleted=True, # INFO - G.M - 2020-17-09 - we do explicitly don't set user here to not # have filter on workspace, in some case we do want user create event on workspace # he doesn't have access: when he remove itself from workspace for example current_user=None, ) workspace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(role.workspace_id)) user_api = UserApi(current_user, context.dbsession, self._config, show_deleted=True) role_api = RoleApi(current_user=current_user, session=context.dbsession, config=self._config) try: user_field = EventApi.user_schema.dump( user_api.get_user_with_context(user_api.get_one( role.user_id))).data except UserDoesNotExist: # It is possible to have an already deleted user when deleting his roles. user_field = None role_in_context = role_api.get_user_role_workspace_with_context(role) fields = { Event.USER_FIELD: user_field, Event.WORKSPACE_FIELD: EventApi.workspace_schema.dump(workspace_in_context).data, Event.MEMBER_FIELD: EventApi.workspace_user_role_schema.dump(role_in_context).data, } event_api = EventApi(current_user, context.dbsession, self._config) event_api.create_event( entity_type=EntityType.WORKSPACE_MEMBER, operation=operation, additional_fields=fields, context=context, )
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_mention_events( cls, mentions: typing.Iterable[Mention], content: Content, context: TracimContext ) -> None: role_api = RoleApi(session=context.dbsession, config=context.app_config, current_user=None) workspace_members_usernames = [ user.username for user in role_api.get_workspace_members(content.workspace_id) ] for mention in mentions: recipient = mention.recipient if ( recipient not in workspace_members_usernames and recipient not in ALL__GROUP_MENTIONS ): raise UserNotMemberOfWorkspace( "This user is not a member of this workspace: {}".format(mention.recipient) ) current_user = context.safe_current_user() content_api = ContentApi(context.dbsession, current_user, context.app_config) content_in_context = content_api.get_content_in_context(content) workspace_api = WorkspaceApi(context.dbsession, current_user, context.app_config) workspace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(content_in_context.workspace.workspace_id) ) content_schema = EventApi.get_content_schema_for_type(content.type) content_dict = content_schema.dump(content_in_context).data common_fields = { Event.CONTENT_FIELD: content_dict, Event.WORKSPACE_FIELD: EventApi.workspace_schema.dump(workspace_in_context).data, } event_api = EventApi(current_user, context.dbsession, context.app_config) for mention in mentions: fields = {cls.MENTION_FIELD: {"recipient": mention.recipient, "id": mention.id}} fields.update(common_fields) event_api.create_event( entity_type=EntityType.MENTION, operation=OperationType.CREATED, additional_fields=fields, context=context, )
def disable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ disable 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.disable_notifications(request.candidate_user, workspace) wapi.save(workspace) return
def guest_upload_info(self, context, request: TracimRequest, hapic_data=None) -> UploadPermissionInContext: """ get somes informations about upload permission """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) return api.get_upload_permission_in_context(upload_permission)
def disable_account_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ disable workspace notification """ app_config = request.registry.settings['CFG'] 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 = wapi.get_one(hapic_data.path.workspace_id) wapi.disable_notifications(request.current_user, workspace) wapi.save(workspace) return
def enable_account_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ enable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.current_user, workspace) rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) rapi.get_one(request.current_user.user_id, workspace.workspace_id) wapi.save(workspace) return
def _get_candidate_workspace( self, user: User, request: 'TracimRequest' ) -> Workspace: """ Get current workspace from request :param user: User who want to check the workspace :param request: pyramid request :return: current workspace """ workspace_id = '' try: if 'new_workspace_id' in request.json_body: workspace_id = request.json_body['new_workspace_id'] if not isinstance(workspace_id, int): if workspace_id.isdecimal(): workspace_id = int(workspace_id) else: raise InvalidWorkspaceId('workspace_id is not a correct integer') # nopep8 if not workspace_id: raise WorkspaceNotFoundInTracimRequest('No new_workspace_id property found in body') # nopep8 wapi = WorkspaceApi( current_user=user, session=request.dbsession, config=request.registry.settings['CFG'], show_deleted=True, ) workspace = wapi.get_one(workspace_id) except JSONDecodeError as exc: raise WorkspaceNotFound('Invalid JSON content') from exc except NoResultFound as exc: raise WorkspaceNotFound( 'Workspace {} does not exist ' 'or is not visible for this user'.format(workspace_id) ) from exc return workspace
def guest_upload_check(self, context, request: TracimRequest, hapic_data=None) -> None: """ check upload password and token validity """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted # we should considered do these check at decorator level workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) api.check_password(upload_permission, password=hapic_data.body.password) return
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 notify_content_update(self, event_actor_id: int, event_content_id: int) -> None: """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim_backend.lib.core.content import ContentApi from tracim_backend.lib.core.user import UserApi user = UserApi(None, config=self.config, session=self.session).get_one(event_actor_id) logger.debug(self, "Content: {}".format(event_content_id)) content_api = ContentApi(current_user=user, session=self.session, config=self.config) content = ContentApi( session=self.session, current_user=user, # TODO - G.M - 2019-04-24 - use a system user instead of the user that has triggered the event config=self.config, show_archived=True, show_deleted=True, ).get_one(event_content_id, content_type_list.Any_SLUG) workspace_api = WorkspaceApi(session=self.session, current_user=user, config=self.config) workpace_in_context = workspace_api.get_workspace_with_context( workspace_api.get_one(content.workspace_id)) main_content = content.parent if content.type == content_type_list.Comment.slug else content notifiable_roles = WorkspaceApi( current_user=user, session=self.session, config=self.config).get_notifiable_roles(content.workspace) if len(notifiable_roles) <= 0: logger.info( self, "Skipping notification as nobody subscribed to in workspace {}" .format(content.workspace.label), ) return logger.info( self, "Generating content {} notification email for {} user(s)".format( content.content_id, len(notifiable_roles)), ) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) for role in notifiable_roles: logger.info( self, "Generating content {} notification email to {}".format( content.content_id, role.user.email), ) translator = Translator(app_config=self.config, default_lang=role.user.lang) _ = translator.get_translation # INFO - G.M - 2017-11-15 - set content_id in header to permit reply # references can have multiple values, but only one in this case. replyto_addr = self.config.EMAIL__NOTIFICATION__REPLY_TO__EMAIL.replace( "{content_id}", str(main_content.content_id)) reference_addr = self.config.EMAIL__NOTIFICATION__REFERENCES__EMAIL.replace( "{content_id}", str(main_content.content_id)) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # content_status = translator.get_translation( main_content.get_status().label) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__CONTENT_UPDATE__SUBJECT) subject = translated_subject.replace( EST.WEBSITE_TITLE, self.config.WEBSITE__TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status) reply_to_label = _( "{username} & all members of {workspace}").format( username=user.display_name, workspace=main_content.workspace.label) content_in_context = content_api.get_content_in_context(content) parent_in_context = None if content.parent_id: parent_in_context = content_api.get_content_in_context( content.parent) body_html = self._build_email_body_for_content( self.config. EMAIL__NOTIFICATION__CONTENT_UPDATE__TEMPLATE__HTML, role, content_in_context, parent_in_context, workpace_in_context, user, translator, ) message = EmailNotificationMessage( subject=subject, from_header=self._get_sender(user), to_header=EmailAddress(role.user.display_name, role.user.email), reply_to=EmailAddress(reply_to_label, replyto_addr), # INFO - G.M - 2017-11-15 # References can theorically have label, but in pratice, references # contains only message_id from parents post in thread. # To link this email to a content we create a virtual parent # in reference who contain the content_id. # INFO - G.M - 2020-04-03 - Enforce angle bracket in references header # we need that to ensure best software compatibility # compat from parsing software references=EmailAddress("", reference_addr, force_angle_bracket=True), body_html=body_html, lang=translator.default_lang, ) self.log_email_notification( msg="an email was created to {}".format(message["To"]), action="{:8s}".format("CREATED"), email_recipient=message["To"], email_subject=message["Subject"], config=self.config, ) send_email_through(self.config, email_sender.send_mail, message)
def notify_content_update( self, event_actor_id: int, event_content_id: int ) -> None: """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim_backend.lib.core.content import ContentApi from tracim_backend.lib.core.user import UserApi user = UserApi( None, config=self.config, session=self.session, ).get_one(event_actor_id) logger.debug(self, 'Content: {}'.format(event_content_id)) content_api = ContentApi( current_user=user, session=self.session, config=self.config, ) content = ContentApi( session=self.session, current_user=user, # nopep8 TODO - use a system user instead of the user that has triggered the event config=self.config, show_archived=True, show_deleted=True, ).get_one(event_content_id, content_type_list.Any_SLUG) workspace_api = WorkspaceApi( session=self.session, current_user=user, config=self.config, ) workpace_in_context = workspace_api.get_workspace_with_context(workspace_api.get_one(content.workspace_id)) # nopep8 main_content = content.parent if content.type == content_type_list.Comment.slug else content # nopep8 notifiable_roles = WorkspaceApi( current_user=user, session=self.session, config=self.config, ).get_notifiable_roles(content.workspace) if len(notifiable_roles) <= 0: logger.info(self, 'Skipping notification as nobody subscribed to in workspace {}'.format(content.workspace.label)) return logger.info(self, 'Generating content {} notification email for {} user(s)'.format( content.content_id, len(notifiable_roles) )) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) for role in notifiable_roles: logger.info(self, 'Generating content {} notification email to {}'.format( content.content_id, role.user.email ) ) translator = Translator(app_config=self.config, default_lang=role.user.lang) # nopep8 _ = translator.get_translation to_addr = formataddr((role.user.display_name, role.user.email)) # INFO - G.M - 2017-11-15 - set content_id in header to permit reply # references can have multiple values, but only one in this case. replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8 '{content_id}', str(main_content.content_id) ) reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8 '{content_id}',str(main_content.content_id) ) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # content_status = translator.get_translation(main_content.get_status().label) translated_subject = translator.get_translation(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT) subject = translated_subject.replace(EST.WEBSITE_TITLE, self.config.WEBSITE_TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status) reply_to_label = _('{username} & all members of {workspace}').format( # nopep8 username=user.display_name, workspace=main_content.workspace.label) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender(user) message['To'] = to_addr message['Reply-to'] = formataddr((reply_to_label, replyto_addr)) # INFO - G.M - 2017-11-15 # References can theorically have label, but in pratice, references # contains only message_id from parents post in thread. # To link this email to a content we create a virtual parent # in reference who contain the content_id. message['References'] = formataddr(('', reference_addr)) content_in_context = content_api.get_content_in_context(content) parent_in_context = None if content.parent_id: parent_in_context = content_api.get_content_in_context(content.parent) # nopep8 body_html = self._build_email_body_for_content( self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content_in_context, parent_in_context, workpace_in_context, user, translator, ) part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part2) self.log_email_notification( msg='an email was created to {}'.format(message['To']), action='{:8s}'.format('CREATED'), email_recipient=message['To'], email_subject=message['Subject'], config=self.config, ) send_email_through( self.config, email_sender.send_mail, message )
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']