def test_unit__crud_caller__ok__user_role_in_workspace(self, session): hook = UserRoleInWorkspaceHookImpl() session.context.plugin_manager.register(hook) owner = User(email="john") workspace = Workspace(label="Hello", owner=owner) session.add(workspace) session.flush() role = UserRoleInWorkspace(role=UserRoleInWorkspace.READER, user=owner, workspace=workspace) session.add(role) session.flush() hook.mock_hooks.assert_called_with("created", role=role, context=session.context) role.role = UserRoleInWorkspace.WORKSPACE_MANAGER session.add(role) session.flush() hook.mock_hooks.assert_called_with("modified", role=role, context=session.context) session.delete(role) session.flush() hook.mock_hooks.assert_called_with("deleted", role=role, context=session.context)
def test__unit__CandidateWorkspaceRoleChecker__err_role_insufficient(self): current_user = User(user_id=2, email='*****@*****.**') current_user.groups.append( Group(group_id=2, group_name=Group.TIM_MANAGER_GROUPNAME)) candidate_workspace = Workspace(workspace_id=3) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=2) self.session.add(current_user) self.session.add(candidate_workspace) self.session.add(role) self.session.flush() transaction.commit() class FakeTracimContext(TracimContext): @property def current_user(self): return current_user @property def candidate_workspace(self): return candidate_workspace assert CandidateWorkspaceRoleChecker(2).check(FakeTracimContext()) with pytest.raises(InsufficientUserRoleInWorkspace): CandidateWorkspaceRoleChecker(3).check(FakeTracimContext()) with pytest.raises(InsufficientUserRoleInWorkspace): CandidateWorkspaceRoleChecker(4).check(FakeTracimContext())
def test__unit__CandidateWorkspaceRoleChecker__ok__nominal_case( self, session): current_user = User(user_id=2, email="*****@*****.**") current_user.profile = Profile.TRUSTED_USER candidate_workspace = Workspace(workspace_id=3, owner=current_user) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=5) session.add(current_user) session.add(candidate_workspace) session.add(role) session.flush() transaction.commit() class FakeBaseFakeTracimContext(BaseFakeTracimContext): @property def current_user(self): return current_user @property def candidate_workspace(self): return candidate_workspace assert CandidateWorkspaceRoleChecker(1).check( FakeBaseFakeTracimContext()) assert CandidateWorkspaceRoleChecker(2).check( FakeBaseFakeTracimContext())
def test__unit__CandidateWorkspaceRoleChecker__err_role_insufficient( self, session): current_user = User(user_id=2, email="*****@*****.**") current_user.profile = Profile.TRUSTED_USER candidate_workspace = Workspace(workspace_id=3, owner=current_user) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=2) session.add(current_user) session.add(candidate_workspace) session.add(role) session.flush() transaction.commit() class FakeBaseFakeTracimContext(BaseFakeTracimContext): @property def current_user(self): return current_user @property def candidate_workspace(self): return candidate_workspace assert CandidateWorkspaceRoleChecker(2).check( FakeBaseFakeTracimContext()) with pytest.raises(InsufficientUserRoleInWorkspace): CandidateWorkspaceRoleChecker(3).check(FakeBaseFakeTracimContext()) with pytest.raises(InsufficientUserRoleInWorkspace): CandidateWorkspaceRoleChecker(4).check(FakeBaseFakeTracimContext())
class RoleUpdateSchema(marshmallow.Schema): role = marshmallow.fields.String( required=True, example='contributor', validate=OneOf(UserRoleInWorkspace.get_all_role_slug())) @post_load def make_role(self, data: typing.Dict[str, typing.Any]) -> object: return RoleUpdate(**data)
def create_one( self, user: User, workspace: Workspace, role_level: int, with_notif: bool, flush: bool=True ) -> UserRoleInWorkspace: # INFO - G.M - 2018-10-29 - Check if role already exist query = self._get_one_rsc(user.user_id, workspace.workspace_id) if query.count() > 0: raise RoleAlreadyExistError( 'Role already exist for user {} in workspace {}.'.format( user.user_id, workspace.workspace_id ) ) role = UserRoleInWorkspace() role.user_id = user.user_id role.workspace = workspace role.role = role_level role.do_notify = with_notif if flush: self._session.flush() return role
def update_role( self, role: UserRoleInWorkspace, role_level: int, with_notif: typing.Optional[bool] = None, save_now: bool = False, ): """ Update role of user in this workspace :param role: UserRoleInWorkspace object :param role_level: level of new role wanted :param with_notif: is user notification enabled in this workspace ? :param save_now: database flush :return: updated role """ role.role = role_level if with_notif is not None: role.do_notify = with_notif if save_now: self.save(role) return role
def update_role( self, role: UserRoleInWorkspace, role_level: int, with_notif: typing.Optional[bool] = None, save_now: bool=False, ): """ Update role of user in this workspace :param role: UserRoleInWorkspace object :param role_level: level of new role wanted :param with_notif: is user notification enabled in this workspace ? :param save_now: database flush :return: updated role """ role.role = role_level if with_notif is not None: role.do_notify = with_notif if save_now: self.save(role) return role
def test__unit__ContentTypeCreationChecker__err__implicit_insufficent_role_in_workspace( self): current_user = User(user_id=2, email="*****@*****.**") current_user.groups.append( Group(group_id=2, group_name=Group.TIM_MANAGER_GROUPNAME)) current_workspace = Workspace(workspace_id=3) candidate_content_type = ContentType( slug="test", fa_icon="", hexcolor="", label="Test", creation_label="Test", available_statuses=[], minimal_role_content_creation=WorkspaceRoles.CONTENT_MANAGER, ) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=WorkspaceRoles.CONTRIBUTOR.level) self.session.add(current_user) self.session.add(current_workspace) self.session.add(role) self.session.flush() transaction.commit() class FakeContentTypeList(object): def get_one_by_slug(self, slug=str) -> ContentType: return candidate_content_type class FakeTracimContext(TracimContext): @property def current_user(self): return current_user @property def current_workspace(self): return current_workspace @property def candidate_content_type(self): return candidate_content_type with pytest.raises(InsufficientUserRoleInWorkspace): assert ContentTypeCreationChecker(FakeContentTypeList()).check( FakeTracimContext())
def _build_context_for_content_update( self, role: UserRoleInWorkspace, content_in_context: ContentInContext, parent_in_context: typing.Optional[ContentInContext], workspace_in_context: WorkspaceInContext, actor: User, translator: Translator, ): _ = translator.get_translation content = content_in_context.content action = content.get_last_action().id previous_revision = content.get_previous_revision() new_status = _(content.get_status().label) workspace_url = workspace_in_context.frontend_url role_label = role.role_as_label() logo_url = get_email_logo_frontend_url(self.config) # FIXME: remove/readapt assert to debug easily broken case # assert user # assert workspace # assert main_title # assert status_label # # assert status_icon_url # assert role_label # # assert content_intro # assert content_text or content_text == content.description # assert logo_url return { "user": role.user, "actor": actor, "action": action, "workspace": role.workspace, "ActionDescription": ActionDescription, "parent_in_context": parent_in_context, "content_in_context": content_in_context, "workspace_url": workspace_url, "previous_revision": previous_revision, "new_status": new_status, "role_label": role_label, "logo_url": logo_url, }
def test__unit__ContentTypeCreationChecker__err__implicit_insufficent_role_in_workspace( self, session): current_user = User(user_id=2, email="*****@*****.**") current_user.profile = Profile.TRUSTED_USER current_workspace = Workspace(workspace_id=3, owner=current_user) candidate_content_type = TracimContentType( slug="test", fa_icon="", label="Test", creation_label="Test", available_statuses=[], minimal_role_content_creation=WorkspaceRoles.CONTENT_MANAGER, ) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=WorkspaceRoles.CONTRIBUTOR.level) session.add(current_user) session.add(current_workspace) session.add(role) session.flush() transaction.commit() class FakeContentTypeList(object): def get_one_by_slug(self, slug=str) -> TracimContentType: return candidate_content_type class FakeBaseFakeTracimContext(BaseFakeTracimContext): @property def current_user(self): return current_user @property def current_workspace(self): return current_workspace @property def candidate_content_type(self): return candidate_content_type with pytest.raises(InsufficientUserRoleInWorkspace): assert ContentTypeCreationChecker(FakeContentTypeList()).check( FakeBaseFakeTracimContext())
def test__unit__ContentTypeCreationChecker__ok__explicit(self): current_user = User(user_id=2, email='*****@*****.**') current_user.groups.append( Group(group_id=2, group_name=Group.TIM_MANAGER_GROUPNAME)) current_workspace = Workspace(workspace_id=3) candidate_content_type = ContentType( slug='test', fa_icon='', hexcolor='', label='Test', creation_label='Test', available_statuses=[], minimal_role_content_creation=WorkspaceRoles.CONTENT_MANAGER) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=WorkspaceRoles.CONTENT_MANAGER.level) self.session.add(current_user) self.session.add(current_workspace) self.session.add(role) self.session.flush() transaction.commit() class FakeContentTypeList(object): def get_one_by_slug(self, slug=str) -> ContentType: return candidate_content_type class FakeTracimContext(TracimContext): @property def current_user(self): return current_user @property def current_workspace(self): return current_workspace assert ContentTypeCreationChecker(FakeContentTypeList(), content_type_slug='test').check( FakeTracimContext())
class WorkspaceMemberSchema(marshmallow.Schema): role = marshmallow.fields.String( example='contributor', validate=OneOf(UserRoleInWorkspace.get_all_role_slug())) user_id = marshmallow.fields.Int( example=3, validate=Range(min=1, error="Value must be greater than 0"), ) workspace_id = marshmallow.fields.Int( example=4, validate=Range(min=1, error="Value must be greater than 0"), ) user = marshmallow.fields.Nested(UserDigestSchema()) workspace = marshmallow.fields.Nested( WorkspaceDigestSchema(exclude=('sidebar_entries', ))) is_active = marshmallow.fields.Bool() do_notify = marshmallow.fields.Bool( description='has user enabled notification for this workspace', example=True, ) class Meta: description = 'Workspace Member information'
class WorkspaceMemberInviteSchema(marshmallow.Schema): role = marshmallow.fields.String( example='contributor', validate=OneOf(UserRoleInWorkspace.get_all_role_slug()), required=True) user_id = marshmallow.fields.Int( example=5, default=None, allow_none=True, ) user_email = marshmallow.fields.Email( example='*****@*****.**', default=None, allow_none=True, ) user_public_name = marshmallow.fields.String( example='John', default=None, allow_none=True, ) @post_load def make_role(self, data: typing.Dict[str, typing.Any]) -> object: return WorkspaceMemberInvitation(**data)
def test__unit__RoleChecker__ok__nominal_case(self): current_user = User(user_id=2, email='*****@*****.**') current_user.groups.append( Group(group_id=2, group_name=Group.TIM_MANAGER_GROUPNAME)) current_workspace = Workspace(workspace_id=3) role = UserRoleInWorkspace(user_id=2, workspace_id=3, role=5) self.session.add(current_user) self.session.add(current_workspace) self.session.add(role) self.session.flush() transaction.commit() class FakeTracimContext(TracimContext): @property def current_user(self): return current_user @property def current_workspace(self): return current_workspace assert RoleChecker(1).check(FakeTracimContext()) assert RoleChecker(2).check(FakeTracimContext())
positive_int_validator = Range(min=0, error="Value must be positive or 0") # String # string matching list of int separated by ',' regex_string_as_list_of_int = Regexp(regex=(re.compile('^(\d+(,\d+)*)?$'))) acp_validator = Length(min=2) not_empty_string_validator = Length(min=1) action_description_validator = OneOf(ActionDescription.allowed_values()) content_global_status_validator = OneOf( [status.value for status in GlobalStatus]) content_status_validator = OneOf(content_status_list.get_all_slugs_values()) user_profile_validator = OneOf(Profile._NAME) user_timezone_validator = Length(max=User.MAX_TIMEZONE_LENGTH) user_email_validator = Length(min=User.MIN_EMAIL_LENGTH, max=User.MAX_EMAIL_LENGTH) user_password_validator = Length(min=User.MIN_PASSWORD_LENGTH, max=User.MAX_PASSWORD_LENGTH) user_public_name_validator = Length(min=User.MIN_PUBLIC_NAME_LENGTH, max=User.MAX_PUBLIC_NAME_LENGTH) user_lang_validator = Length(min=User.MIN_LANG_LENGTH, max=User.MAX_LANG_LENGTH) user_role_validator = OneOf(UserRoleInWorkspace.get_all_role_slug()) # Dynamic validator # all_content_types_validator = OneOf(choices=[]) def update_validators(): all_content_types_validator.choices = content_type_list.endpoint_allowed_types_slug( ) # nopep8
def _build_context_for_content_update( self, role: UserRoleInWorkspace, content_in_context: ContentInContext, parent_in_context: typing.Optional[ContentInContext], workspace_in_context: WorkspaceInContext, actor: User, translator: Translator ): _ = translator.get_translation content = content_in_context.content action = content.get_last_action().id # default values user = role.user workspace = role.workspace workspace_url = workspace_in_context.frontend_url main_title = content.label status_label = content.get_status().label # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url # nopep8 status_icon_url = '' role_label = role.role_as_label() content_intro = '<span id="content-intro-username">{}</span> did something.'.format(actor.display_name) # nopep8 content_text = content.description call_to_action_url = content_in_context.frontend_url logo_url = get_email_logo_frontend_url(self.config) if ActionDescription.COMMENT == action: main_title = parent_in_context.label content_intro = '' call_to_action_url = parent_in_context.frontend_url elif ActionDescription.STATUS_UPDATE == action: new_status = translator.get_translation(content.get_status().label) main_title = content_in_context.label content_intro = _('I modified the status of <i>{content}</i>. The new status is <i>{new_status}</i>').format( content=content.get_label(), new_status=new_status ) content_text = '' call_to_action_url = content_in_context.frontend_url elif ActionDescription.CREATION == action: main_title = content_in_context.label content_intro = _('I added an item entitled <i>{content}</i>.').format(content=content.get_label()) # nopep8 content_text = '' elif action in (ActionDescription.REVISION, ActionDescription.EDITION): main_title = content_in_context.label content_intro = _('I updated <i>{content}</i>.').format(content=content.get_label()) # nopep8 previous_revision = content.get_previous_revision() title_diff = htmldiff(previous_revision.label, content.label) content_diff = htmldiff(previous_revision.description, content.description) if title_diff or content_diff: content_text = str('<p>{diff_intro_text}</p>\n{title_diff}\n{content_diff}').format( # nopep8 diff_intro_text=_('Here is an overview of the changes:'), title_diff=title_diff, content_diff=content_diff ) # if not content_intro and not content_text: # # Skip notification, but it's not normal # logger.error( # self, # 'A notification is being sent but no content. ' # 'Here are some debug informations: [content_id: {cid}]' # '[action: {act}][author: {actor}]'.format( # cid=content.content_id, # act=action, # actor=actor # ) # ) # raise EmptyNotificationError('Unexpected empty notification') # FIXME: remove/readapt assert to debug easily broken case assert user assert workspace assert main_title assert status_label # assert status_icon_url assert role_label # assert content_intro assert content_text or content_text == content.description assert call_to_action_url assert logo_url return { 'user': role.user, 'workspace': role.workspace, 'workspace_url': workspace_url, 'main_title': main_title, 'status_label': status_label, 'status_icon_url': status_icon_url, 'role_label': role_label, 'content_intro': content_intro, 'content_text': content_text, 'call_to_action_url': call_to_action_url, 'logo_url': logo_url, }
not_empty_string_validator = Length(min=1) action_description_validator = OneOf(ActionDescription.allowed_values()) content_global_status_validator = OneOf([status.value for status in GlobalStatus]) content_status_validator = OneOf(content_status_list.get_all_slugs_values()) user_profile_validator = OneOf(Profile._NAME) user_timezone_validator = Length(max=User.MAX_TIMEZONE_LENGTH) user_email_validator = Length( min=User.MIN_EMAIL_LENGTH, max=User.MAX_EMAIL_LENGTH ) user_password_validator = Length( min=User.MIN_PASSWORD_LENGTH, max=User.MAX_PASSWORD_LENGTH ) user_public_name_validator = Length( min=User.MIN_PUBLIC_NAME_LENGTH, max=User.MAX_PUBLIC_NAME_LENGTH ) user_lang_validator = Length( min=User.MIN_LANG_LENGTH, max=User.MAX_LANG_LENGTH ) user_role_validator = OneOf(UserRoleInWorkspace.get_all_role_slug()) # Dynamic validator # all_content_types_validator = OneOf(choices=[]) def update_validators(): all_content_types_validator.choices = content_type_list.endpoint_allowed_types_slug() # nopep8
def _build_context_for_content_update( self, role: UserRoleInWorkspace, content_in_context: ContentInContext, parent_in_context: typing.Optional[ContentInContext], workspace_in_context: WorkspaceInContext, actor: User, translator: Translator ): _ = translator.get_translation content = content_in_context.content action = content.get_last_action().id # default values user = role.user workspace = role.workspace workspace_url = workspace_in_context.frontend_url main_title = content.label status_label = content.get_status().label # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url # nopep8 status_icon_url = '' role_label = role.role_as_label() content_intro = '<span id="content-intro-username">{}</span> did something.'.format(actor.display_name) # nopep8 content_text = content.description call_to_action_text = 'See more' call_to_action_url = content_in_context.frontend_url logo_url = get_email_logo_frontend_url(self.config) if ActionDescription.CREATION == action: call_to_action_text = _('View online') content_intro = _('<span id="content-intro-username">{}</span> create a content:').format(actor.display_name) # nopep8 if content_type_list.Thread.slug == content.type: if content.get_last_comment_from(actor): content_text = content.get_last_comment_from(actor).description # nopep8 call_to_action_text = _('Answer') content_intro = _('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name) content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + content_text # nopep8 elif content_type_list.File.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name) if content.description: content_text = content.description else: content_text = '<span id="content-body-only-title">{}</span>'.format(content.label) elif content_type_list.Page.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name) content_text = '<span id="content-body-only-title">{}</span>'.format(content.label) elif ActionDescription.REVISION == action: content_text = content.description call_to_action_text = _('View online') if content_type_list.File.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name) content_text = content.description elif ActionDescription.EDITION == action: call_to_action_text = _('View online') if content_type_list.File.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name) content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + content.description # nopep8 elif content_type_list.Thread.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name) previous_revision = content.get_previous_revision() title_diff = '' if previous_revision.label != content.label: title_diff = htmldiff(previous_revision.label, content.label) content_text = str('<p id="content-body-intro">{}</p> {text} {title_diff} {content_diff}').format( text=_('Here is an overview of the changes:'), title_diff=title_diff, content_diff=htmldiff(previous_revision.description, content.description) ) elif content_type_list.Page.slug == content.type: content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name) previous_revision = content.get_previous_revision() title_diff = '' if previous_revision.label != content.label: title_diff = htmldiff(previous_revision.label, content.label) # nopep8 content_text = str('<p id="content-body-intro">{}</p> {text}</p> {title_diff} {content_diff}').format( # nopep8 actor.display_name, text=_('Here is an overview of the changes:'), title_diff=title_diff, content_diff=htmldiff(previous_revision.description, content.description) ) elif ActionDescription.STATUS_UPDATE == action: intro_user_msg = _( '<span id="content-intro-username">{}</span> ' 'updated the following status:' ) intro_body_msg = '<p id="content-body-intro">{}: {}</p>' call_to_action_text = _('View online') content_intro = intro_user_msg.format(actor.display_name) content_text = intro_body_msg.format( content.get_label(), content.get_status().label, ) elif ActionDescription.COMMENT == action: call_to_action_text = _('Answer') main_title = parent_in_context.label content_intro = _('<span id="content-intro-username">{}</span> added a comment:').format(actor.display_name) # nopep8 call_to_action_url = parent_in_context.frontend_url if not content_intro and not content_text: # Skip notification, but it's not normal logger.error( self, 'A notification is being sent but no content. ' 'Here are some debug informations: [content_id: {cid}]' '[action: {act}][author: {actor}]'.format( cid=content.content_id, act=action, actor=actor ) ) raise EmptyNotificationError('Unexpected empty notification') # FIXME: remove/readapt assert to debug easily broken case assert user assert workspace assert main_title assert status_label # assert status_icon_url assert role_label assert content_intro assert content_text or content_text == content.description assert call_to_action_text assert call_to_action_url assert logo_url return { 'user': role.user, 'workspace': role.workspace, 'workspace_url': workspace_url, 'main_title': main_title, 'status_label': status_label, 'status_icon_url': status_icon_url, 'role_label': role_label, 'content_intro': content_intro, 'content_text': content_text, 'call_to_action_text': call_to_action_text, 'call_to_action_url': call_to_action_url, 'logo_url': logo_url, }