Преглед изворни кода

Merge branch 'develop' of github.com:tracim/tracim_backend into feature/671_read_unread_endpoints

Guénaël Muller пре 6 година
родитељ
комит
405989f80e

+ 13 - 0
tracim/exceptions.py Прегледај датотеку

163
 class EmptyCommentContentNotAllowed(EmptyValueNotAllowed):
163
 class EmptyCommentContentNotAllowed(EmptyValueNotAllowed):
164
     pass
164
     pass
165
 
165
 
166
+
167
+class RoleDoesNotExist(TracimException):
168
+    pass
169
+
170
+
171
+class EmailValidationFailed(TracimException):
172
+    pass
173
+
174
+
175
+class UserCreationFailed(TracimException):
176
+    pass
177
+
178
+
166
 class ParentNotFound(NotFound):
179
 class ParentNotFound(NotFound):
167
     pass
180
     pass

+ 64 - 1
tracim/lib/core/user.py Прегледај датотеку

6
 import typing as typing
6
 import typing as typing
7
 
7
 
8
 from tracim.exceptions import NotificationNotSend
8
 from tracim.exceptions import NotificationNotSend
9
+from tracim.exceptions import EmailValidationFailed
9
 from tracim.lib.mail_notifier.notifier import get_email_manager
10
 from tracim.lib.mail_notifier.notifier import get_email_manager
10
 from sqlalchemy.orm import Session
11
 from sqlalchemy.orm import Session
11
 
12
 
13
 from tracim.models.auth import User
14
 from tracim.models.auth import User
14
 from tracim.models.auth import Group
15
 from tracim.models.auth import Group
15
 from sqlalchemy.orm.exc import NoResultFound
16
 from sqlalchemy.orm.exc import NoResultFound
16
-from tracim.exceptions import WrongUserPassword, UserDoesNotExist
17
+from tracim.exceptions import UserDoesNotExist
18
+from tracim.exceptions import WrongUserPassword
17
 from tracim.exceptions import AuthenticationFailed
19
 from tracim.exceptions import AuthenticationFailed
18
 from tracim.models.context_models import UserInContext
20
 from tracim.models.context_models import UserInContext
21
+from tracim.models.context_models import TypeUser
19
 
22
 
20
 
23
 
21
 class UserApi(object):
24
 class UserApi(object):
68
             raise UserDoesNotExist('User "{}" not found in database'.format(email)) from exc  # nopep8
71
             raise UserDoesNotExist('User "{}" not found in database'.format(email)) from exc  # nopep8
69
         return user
72
         return user
70
 
73
 
74
+    def get_one_by_public_name(self, public_name: str) -> User:
75
+        """
76
+        Get one user by public_name
77
+        """
78
+        try:
79
+            user = self._base_query().filter(User.display_name == public_name).one()
80
+        except NoResultFound as exc:
81
+            raise UserDoesNotExist('User "{}" not found in database'.format(public_name)) from exc  # nopep8
82
+        return user
71
     # FIXME - G.M - 24-04-2018 - Duplicate method with get_one.
83
     # FIXME - G.M - 24-04-2018 - Duplicate method with get_one.
84
+
72
     def get_one_by_id(self, id: int) -> User:
85
     def get_one_by_id(self, id: int) -> User:
73
         return self.get_one(user_id=id)
86
         return self.get_one(user_id=id)
74
 
87
 
83
     def get_all(self) -> typing.Iterable[User]:
96
     def get_all(self) -> typing.Iterable[User]:
84
         return self._session.query(User).order_by(User.display_name).all()
97
         return self._session.query(User).order_by(User.display_name).all()
85
 
98
 
99
+    def find(
100
+            self,
101
+            user_id: int=None,
102
+            email: str=None,
103
+            public_name: str=None
104
+    ) -> typing.Tuple[TypeUser, User]:
105
+        """
106
+        Find existing user from all theses params.
107
+        Check is made in this order: user_id, email, public_name
108
+        If no user found raise UserDoesNotExist exception
109
+        """
110
+        user = None
111
+
112
+        if user_id:
113
+            try:
114
+                user = self.get_one(user_id)
115
+                return TypeUser.USER_ID, user
116
+            except UserDoesNotExist:
117
+                pass
118
+        if email:
119
+            try:
120
+                user = self.get_one_by_email(email)
121
+                return TypeUser.EMAIL, user
122
+            except UserDoesNotExist:
123
+                pass
124
+        if public_name:
125
+            try:
126
+                user = self.get_one_by_public_name(public_name)
127
+                return TypeUser.PUBLIC_NAME, user
128
+            except UserDoesNotExist:
129
+                pass
130
+
131
+        raise UserDoesNotExist('User not found with any of given params.')
132
+
86
     # Check methods
133
     # Check methods
87
 
134
 
88
     def user_with_email_exists(self, email: str) -> bool:
135
     def user_with_email_exists(self, email: str) -> bool:
112
 
159
 
113
     # Actions
160
     # Actions
114
 
161
 
162
+    def _check_email(self, email: str) -> bool:
163
+        # TODO - G.M - 2018-07-05 - find a better way to check email
164
+        if not email:
165
+            return False
166
+        email = email.split('@')
167
+        if len(email) != 2:
168
+            return False
169
+        return True
170
+
115
     def update(
171
     def update(
116
             self,
172
             self,
117
             user: User,
173
             user: User,
125
             user.display_name = name
181
             user.display_name = name
126
 
182
 
127
         if email is not None:
183
         if email is not None:
184
+            email_exist = self._check_email(email)
185
+            if not email_exist:
186
+                raise EmailValidationFailed('Email given form {} is uncorrect'.format(email))  # nopep8
128
             user.email = email
187
             user.email = email
129
 
188
 
130
         if password is not None:
189
         if password is not None:
176
         """Previous create_user method"""
235
         """Previous create_user method"""
177
         user = User()
236
         user = User()
178
 
237
 
238
+        email_exist = self._check_email(email)
239
+        if not email_exist:
240
+            raise EmailValidationFailed('Email given form {} is uncorrect'.format(email))  # nopep8
179
         user.email = email
241
         user.email = email
242
+        user.display_name = email.split('@')[0]
180
 
243
 
181
         for group in groups:
244
         for group in groups:
182
             user.groups.append(group)
245
             user.groups.append(group)

+ 108 - 72
tracim/lib/core/userworkspace.py Прегледај датотеку

3
 
3
 
4
 from tracim import CFG
4
 from tracim import CFG
5
 from tracim.models.context_models import UserRoleWorkspaceInContext
5
 from tracim.models.context_models import UserRoleWorkspaceInContext
6
+from tracim.models.roles import WorkspaceRoles
6
 
7
 
7
 __author__ = 'damien'
8
 __author__ = 'damien'
8
 
9
 
11
 from tracim.models.auth import User
12
 from tracim.models.auth import User
12
 from tracim.models.data import Workspace
13
 from tracim.models.data import Workspace
13
 from tracim.models.data import UserRoleInWorkspace
14
 from tracim.models.data import UserRoleInWorkspace
14
-from tracim.models.data import RoleType
15
 
15
 
16
 
16
 
17
 class RoleApi(object):
17
 class RoleApi(object):
18
 
18
 
19
-    ALL_ROLE_VALUES = UserRoleInWorkspace.get_all_role_values()
19
+    # TODO - G.M - 29-06-2018 - [Cleanup] Drop this
20
+    # ALL_ROLE_VALUES = UserRoleInWorkspace.get_all_role_values()
20
     # Dict containing readable members roles for given role
21
     # Dict containing readable members roles for given role
21
-    members_read_rights = {
22
-        UserRoleInWorkspace.NOT_APPLICABLE: [],
23
-        UserRoleInWorkspace.READER: [
24
-            UserRoleInWorkspace.WORKSPACE_MANAGER,
25
-        ],
26
-        UserRoleInWorkspace.CONTRIBUTOR: [
27
-            UserRoleInWorkspace.WORKSPACE_MANAGER,
28
-            UserRoleInWorkspace.CONTENT_MANAGER,
29
-            UserRoleInWorkspace.CONTRIBUTOR,
30
-        ],
31
-        UserRoleInWorkspace.CONTENT_MANAGER: [
32
-            UserRoleInWorkspace.WORKSPACE_MANAGER,
33
-            UserRoleInWorkspace.CONTENT_MANAGER,
34
-            UserRoleInWorkspace.CONTRIBUTOR,
35
-            UserRoleInWorkspace.READER,
36
-        ],
37
-        UserRoleInWorkspace.WORKSPACE_MANAGER: [
38
-            UserRoleInWorkspace.WORKSPACE_MANAGER,
39
-            UserRoleInWorkspace.CONTENT_MANAGER,
40
-            UserRoleInWorkspace.CONTRIBUTOR,
41
-            UserRoleInWorkspace.READER,
42
-        ],
43
-    }
22
+    # members_read_rights = {
23
+    #     UserRoleInWorkspace.NOT_APPLICABLE: [],
24
+    #     UserRoleInWorkspace.READER: [
25
+    #         UserRoleInWorkspace.WORKSPACE_MANAGER,
26
+    #     ],
27
+    #     UserRoleInWorkspace.CONTRIBUTOR: [
28
+    #         UserRoleInWorkspace.WORKSPACE_MANAGER,
29
+    #         UserRoleInWorkspace.CONTENT_MANAGER,
30
+    #         UserRoleInWorkspace.CONTRIBUTOR,
31
+    #     ],
32
+    #     UserRoleInWorkspace.CONTENT_MANAGER: [
33
+    #         UserRoleInWorkspace.WORKSPACE_MANAGER,
34
+    #         UserRoleInWorkspace.CONTENT_MANAGER,
35
+    #         UserRoleInWorkspace.CONTRIBUTOR,
36
+    #         UserRoleInWorkspace.READER,
37
+    #     ],
38
+    #     UserRoleInWorkspace.WORKSPACE_MANAGER: [
39
+    #         UserRoleInWorkspace.WORKSPACE_MANAGER,
40
+    #         UserRoleInWorkspace.CONTENT_MANAGER,
41
+    #         UserRoleInWorkspace.CONTRIBUTOR,
42
+    #         UserRoleInWorkspace.READER,
43
+    #     ],
44
+    # }
45
+
46
+    # TODO - G.M - 29-06-2018 - [Cleanup] Drop this
47
+    # @classmethod
48
+    # def role_can_read_member_role(cls, reader_role: int, tested_role: int) \
49
+    #         -> bool:
50
+    #     """
51
+    #     :param reader_role: role as viewer
52
+    #     :param tested_role: role as viwed
53
+    #     :return: True if given role can view member role in workspace.
54
+    #     """
55
+    #     if reader_role in cls.members_read_rights:
56
+    #         return tested_role in cls.members_read_rights[reader_role]
57
+    #     return False
44
 
58
 
45
     def get_user_role_workspace_with_context(
59
     def get_user_role_workspace_with_context(
46
             self,
60
             self,
47
-            user_role: UserRoleInWorkspace
61
+            user_role: UserRoleInWorkspace,
62
+            newly_created:bool = None,
63
+            email_sent: bool = None,
48
     ) -> UserRoleWorkspaceInContext:
64
     ) -> UserRoleWorkspaceInContext:
49
         """
65
         """
50
         Return WorkspaceInContext object from Workspace
66
         Return WorkspaceInContext object from Workspace
54
             user_role=user_role,
70
             user_role=user_role,
55
             dbsession=self._session,
71
             dbsession=self._session,
56
             config=self._config,
72
             config=self._config,
73
+            newly_created=newly_created,
74
+            email_sent=email_sent,
57
         )
75
         )
58
         return workspace
76
         return workspace
59
 
77
 
60
-    @classmethod
61
-    def role_can_read_member_role(cls, reader_role: int, tested_role: int) \
62
-            -> bool:
63
-        """
64
-        :param reader_role: role as viewer
65
-        :param tested_role: role as viwed
66
-        :return: True if given role can view member role in workspace.
67
-        """
68
-        if reader_role in cls.members_read_rights:
69
-            return tested_role in cls.members_read_rights[reader_role]
70
-        return False
71
-
72
-    @classmethod
73
-    def create_role(cls) -> UserRoleInWorkspace:
74
-        role = UserRoleInWorkspace()
75
-
76
-        return role
77
-
78
     def __init__(
78
     def __init__(
79
         self,
79
         self,
80
         session: Session,
80
         session: Session,
98
     def get_one(self, user_id: int, workspace_id: int) -> UserRoleInWorkspace:
98
     def get_one(self, user_id: int, workspace_id: int) -> UserRoleInWorkspace:
99
         return self._get_one_rsc(user_id, workspace_id).one()
99
         return self._get_one_rsc(user_id, workspace_id).one()
100
 
100
 
101
+    def update_role(
102
+        self,
103
+        role: UserRoleInWorkspace,
104
+        role_level: int,
105
+        with_notif: typing.Optional[bool] = None,
106
+        save_now: bool=False,
107
+    ):
108
+        """
109
+        Update role of user in this workspace
110
+        :param role: UserRoleInWorkspace object
111
+        :param role_level: level of new role wanted
112
+        :param with_notif: is user notification enabled in this workspace ?
113
+        :param save_now: database flush
114
+        :return: updated role
115
+        """
116
+        role.role = role_level
117
+        if with_notif is not None:
118
+            role.do_notify == with_notif
119
+        if save_now:
120
+            self.save(role)
121
+
122
+        return role
123
+
101
     def create_one(
124
     def create_one(
102
         self,
125
         self,
103
         user: User,
126
         user: User,
106
         with_notif: bool,
129
         with_notif: bool,
107
         flush: bool=True
130
         flush: bool=True
108
     ) -> UserRoleInWorkspace:
131
     ) -> UserRoleInWorkspace:
109
-        role = self.create_role()
132
+        role = UserRoleInWorkspace()
110
         role.user_id = user.user_id
133
         role.user_id = user.user_id
111
         role.workspace = workspace
134
         role.workspace = workspace
112
         role.role = role_level
135
         role.role = role_level
120
         if flush:
143
         if flush:
121
             self._session.flush()
144
             self._session.flush()
122
 
145
 
123
-    def _get_all_for_user(self, user_id) -> typing.List[UserRoleInWorkspace]:
124
-        return self._session.query(UserRoleInWorkspace)\
125
-            .filter(UserRoleInWorkspace.user_id == user_id)
126
-
127
-    def get_all_for_user(self, user: User) -> typing.List[UserRoleInWorkspace]:
128
-        return self._get_all_for_user(user.user_id).all()
129
-
130
-    def get_all_for_user_order_by_workspace(
131
-        self,
132
-        user_id: int
133
-    ) -> typing.List[UserRoleInWorkspace]:
134
-        return self._get_all_for_user(user_id)\
135
-            .join(UserRoleInWorkspace.workspace).order_by(Workspace.label).all()
136
-
137
     def get_all_for_workspace(
146
     def get_all_for_workspace(
138
         self,
147
         self,
139
         workspace:Workspace
148
         workspace:Workspace
145
     def save(self, role: UserRoleInWorkspace) -> None:
154
     def save(self, role: UserRoleInWorkspace) -> None:
146
         self._session.flush()
155
         self._session.flush()
147
 
156
 
148
-    # TODO - G.M - 07-06-2018 - [Cleanup] Check if this method is already needed
149
-    @classmethod
150
-    def get_roles_for_select_field(cls) -> typing.List[RoleType]:
151
-        """
152
-
153
-        :return: list of DictLikeClass instances representing available Roles
154
-        (to be used in select fields)
155
-        """
156
-        result = list()
157
 
157
 
158
-        for role_id in UserRoleInWorkspace.get_all_role_values():
159
-            role = RoleType(role_id)
160
-            result.append(role)
158
+    # TODO - G.M - 29-06-2018 - [Cleanup] Drop this
159
+    # @classmethod
160
+    # def role_can_read_member_role(cls, reader_role: int, tested_role: int) \
161
+    #         -> bool:
162
+    #     """
163
+    #     :param reader_role: role as viewer
164
+    #     :param tested_role: role as viwed
165
+    #     :return: True if given role can view member role in workspace.
166
+    #     """
167
+    #     if reader_role in cls.members_read_rights:
168
+    #         return tested_role in cls.members_read_rights[reader_role]
169
+    #     return False
170
+    # def _get_all_for_user(self, user_id) -> typing.List[UserRoleInWorkspace]:
171
+    #     return self._session.query(UserRoleInWorkspace)\
172
+    #         .filter(UserRoleInWorkspace.user_id == user_id)
173
+    #
174
+    # def get_all_for_user(self, user: User) -> typing.List[UserRoleInWorkspace]:
175
+    #     return self._get_all_for_user(user.user_id).all()
176
+    #
177
+    # def get_all_for_user_order_by_workspace(
178
+    #     self,
179
+    #     user_id: int
180
+    # ) -> typing.List[UserRoleInWorkspace]:
181
+    #     return self._get_all_for_user(user_id)\
182
+    #         .join(UserRoleInWorkspace.workspace).order_by(Workspace.label).all()
161
 
183
 
162
-        return result
184
+    # TODO - G.M - 07-06-2018 - [Cleanup] Check if this method is already needed
185
+    # @classmethod
186
+    # def get_roles_for_select_field(cls) -> typing.List[RoleType]:
187
+    #     """
188
+    #
189
+    #     :return: list of DictLikeClass instances representing available Roles
190
+    #     (to be used in select fields)
191
+    #     """
192
+    #     result = list()
193
+    #
194
+    #     for role_id in UserRoleInWorkspace.get_all_role_values():
195
+    #         role = RoleType(role_id)
196
+    #         result.append(role)
197
+    #
198
+    #     return result

+ 27 - 1
tracim/lib/core/workspace.py Прегледај датотеку

5
 from sqlalchemy.orm import Session
5
 from sqlalchemy.orm import Session
6
 
6
 
7
 from tracim import CFG
7
 from tracim import CFG
8
+from tracim.exceptions import EmptyLabelNotAllowed
8
 from tracim.lib.utils.translation import fake_translator as _
9
 from tracim.lib.utils.translation import fake_translator as _
9
 
10
 
10
 from tracim.lib.core.userworkspace import RoleApi
11
 from tracim.lib.core.userworkspace import RoleApi
69
             save_now: bool=False,
70
             save_now: bool=False,
70
     ) -> Workspace:
71
     ) -> Workspace:
71
         if not label:
72
         if not label:
72
-            label = self.generate_label()
73
+            raise EmptyLabelNotAllowed('Workspace label cannot be empty')
73
 
74
 
74
         workspace = Workspace()
75
         workspace = Workspace()
75
         workspace.label = label
76
         workspace.label = label
105
 
106
 
106
         return workspace
107
         return workspace
107
 
108
 
109
+    def update_workspace(
110
+            self,
111
+            workspace: Workspace,
112
+            label: str,
113
+            description: str,
114
+            save_now: bool=False,
115
+    ) -> Workspace:
116
+        """
117
+        Update workspace
118
+        :param workspace: workspace to update
119
+        :param label: new label of workspace
120
+        :param description: new description
121
+        :param save_now: database flush
122
+        :return: updated workspace
123
+        """
124
+        if not label:
125
+            raise EmptyLabelNotAllowed('Workspace label cannot be empty')
126
+        workspace.label = label
127
+        workspace.description = description
128
+
129
+        if save_now:
130
+            self.save(workspace)
131
+
132
+        return workspace
133
+
108
     def get_one(self, id):
134
     def get_one(self, id):
109
         return self._base_query().filter(Workspace.workspace_id == id).one()
135
         return self._base_query().filter(Workspace.workspace_id == id).one()
110
 
136
 

+ 64 - 5
tracim/models/context_models.py Прегледај датотеку

1
 # coding=utf-8
1
 # coding=utf-8
2
 import typing
2
 import typing
3
 from datetime import datetime
3
 from datetime import datetime
4
+from enum import Enum
4
 
5
 
5
 from slugify import slugify
6
 from slugify import slugify
6
 from sqlalchemy.orm import Session
7
 from sqlalchemy.orm import Session
9
 from tracim.models.auth import Profile
10
 from tracim.models.auth import Profile
10
 from tracim.models.data import Content
11
 from tracim.models.data import Content
11
 from tracim.models.data import ContentRevisionRO
12
 from tracim.models.data import ContentRevisionRO
12
-from tracim.models.data import Workspace, UserRoleInWorkspace
13
+from tracim.models.data import Workspace
14
+from tracim.models.data import UserRoleInWorkspace
15
+from tracim.models.roles import WorkspaceRoles
13
 from tracim.models.workspace_menu_entries import default_workspace_menu_entry
16
 from tracim.models.workspace_menu_entries import default_workspace_menu_entry
14
 from tracim.models.workspace_menu_entries import WorkspaceMenuEntry
17
 from tracim.models.workspace_menu_entries import WorkspaceMenuEntry
15
 from tracim.models.contents import ContentTypeLegacy as ContentType
18
 from tracim.models.contents import ContentTypeLegacy as ContentType
43
         self.workspace_id = workspace_id
46
         self.workspace_id = workspace_id
44
 
47
 
45
 
48
 
46
-class UserWorkspacePath(object):
49
+class WorkspaceAndUserPath(object):
47
     """
50
     """
48
-    Paths params with user_id and workspace id model
51
+    Paths params with workspace id and user_id
49
     """
52
     """
50
-    def __init__(self, user_id: int, workspace_id: int) -> None:
53
+    def __init__(self, workspace_id: int, user_id: int):
51
         self.workspace_id = workspace_id
54
         self.workspace_id = workspace_id
52
         self.user_id = workspace_id
55
         self.user_id = workspace_id
53
 
56
 
120
         self.contents_ids = contents_ids
123
         self.contents_ids = contents_ids
121
 
124
 
122
 
125
 
126
+class RoleUpdate(object):
127
+    """
128
+    Update role
129
+    """
130
+    def __init__(
131
+        self,
132
+        role: str,
133
+    ):
134
+        self.role = role
135
+
136
+
137
+class WorkspaceMemberInvitation(object):
138
+    """
139
+    Workspace Member Invitation
140
+    """
141
+    def __init__(
142
+        self,
143
+        user_id: int,
144
+        user_email_or_public_name: str,
145
+        role: str,
146
+    ):
147
+        self.role = role
148
+        self.user_email_or_public_name = user_email_or_public_name
149
+        self.user_id = user_id
150
+
151
+
152
+class WorkspaceUpdate(object):
153
+    """
154
+    Update workspace
155
+    """
156
+    def __init__(
157
+        self,
158
+        label: str,
159
+        description: str,
160
+    ):
161
+        self.label = label
162
+        self.description = description
163
+
164
+
123
 class ContentCreation(object):
165
 class ContentCreation(object):
124
     """
166
     """
125
     Content creation model
167
     Content creation model
170
         self.raw_content = raw_content
212
         self.raw_content = raw_content
171
 
213
 
172
 
214
 
215
+class TypeUser(Enum):
216
+    """Params used to find user"""
217
+    USER_ID = 'found_id'
218
+    EMAIL = 'found_email'
219
+    PUBLIC_NAME = 'found_public_name'
220
+
221
+
173
 class UserInContext(object):
222
 class UserInContext(object):
174
     """
223
     """
175
     Interface to get User data and User data related to context.
224
     Interface to get User data and User data related to context.
299
             user_role: UserRoleInWorkspace,
348
             user_role: UserRoleInWorkspace,
300
             dbsession: Session,
349
             dbsession: Session,
301
             config: CFG,
350
             config: CFG,
351
+            # Extended params
352
+            newly_created: bool = None,
353
+            email_sent: bool = None
302
     )-> None:
354
     )-> None:
303
         self.user_role = user_role
355
         self.user_role = user_role
304
         self.dbsession = dbsession
356
         self.dbsession = dbsession
305
         self.config = config
357
         self.config = config
358
+        # Extended params
359
+        self.newly_created = newly_created
360
+        self.email_sent = email_sent
306
 
361
 
307
     @property
362
     @property
308
     def user_id(self) -> int:
363
     def user_id(self) -> int:
342
         'contributor', 'content-manager', 'workspace-manager'
397
         'contributor', 'content-manager', 'workspace-manager'
343
         :return: user workspace role as slug.
398
         :return: user workspace role as slug.
344
         """
399
         """
345
-        return UserRoleInWorkspace.SLUG[self.user_role.role]
400
+        return WorkspaceRoles.get_role_from_level(self.user_role.role).slug
401
+
402
+    @property
403
+    def is_active(self) -> bool:
404
+        return self.user.is_active
346
 
405
 
347
     @property
406
     @property
348
     def user(self) -> UserInContext:
407
     def user(self) -> UserInContext:

+ 31 - 32
tracim/models/data.py Прегледај датотеку

30
 from tracim.exceptions import ContentRevisionUpdateError
30
 from tracim.exceptions import ContentRevisionUpdateError
31
 from tracim.models.meta import DeclarativeBase
31
 from tracim.models.meta import DeclarativeBase
32
 from tracim.models.auth import User
32
 from tracim.models.auth import User
33
+from tracim.models.roles import WorkspaceRoles
33
 
34
 
34
 DEFAULT_PROPERTIES = dict(
35
 DEFAULT_PROPERTIES = dict(
35
     allowed_content=dict(
36
     allowed_content=dict(
124
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='roles', lazy='joined')
125
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='roles', lazy='joined')
125
     user = relationship('User', remote_side=[User.user_id], backref='roles')
126
     user = relationship('User', remote_side=[User.user_id], backref='roles')
126
 
127
 
127
-    NOT_APPLICABLE = 0
128
-    READER = 1
129
-    CONTRIBUTOR = 2
130
-    CONTENT_MANAGER = 4
131
-    WORKSPACE_MANAGER = 8
132
-
133
-    SLUG = {
134
-        NOT_APPLICABLE: 'not-applicable',
135
-        READER: 'reader',
136
-        CONTRIBUTOR: 'contributor',
137
-        CONTENT_MANAGER: 'content-manager',
138
-        WORKSPACE_MANAGER: 'workspace-manager',
139
-    }
128
+    NOT_APPLICABLE = WorkspaceRoles.NOT_APPLICABLE.level
129
+    READER = WorkspaceRoles.READER.level
130
+    CONTRIBUTOR = WorkspaceRoles.CONTRIBUTOR.level
131
+    CONTENT_MANAGER = WorkspaceRoles.CONTENT_MANAGER.level
132
+    WORKSPACE_MANAGER = WorkspaceRoles.WORKSPACE_MANAGER.level
133
+
134
+    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
135
+    # SLUG = {
136
+    #     NOT_APPLICABLE: 'not-applicable',
137
+    #     READER: 'reader',
138
+    #     CONTRIBUTOR: 'contributor',
139
+    #     CONTENT_MANAGER: 'content-manager',
140
+    #     WORKSPACE_MANAGER: 'workspace-manager',
141
+    # }
140
 
142
 
141
-    LABEL = dict()
142
-    LABEL[0] = l_('N/A')
143
-    LABEL[1] = l_('Reader')
144
-    LABEL[2] = l_('Contributor')
145
-    LABEL[4] = l_('Content Manager')
146
-    LABEL[8] = l_('Workspace Manager')
143
+    # LABEL = dict()
144
+    # LABEL[0] = l_('N/A')
145
+    # LABEL[1] = l_('Reader')
146
+    # LABEL[2] = l_('Contributor')
147
+    # LABEL[4] = l_('Content Manager')
148
+    # LABEL[8] = l_('Workspace Manager')
147
     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
149
     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
148
     #
150
     #
149
     # STYLE = dict()
151
     # STYLE = dict()
170
     #     return UserRoleInWorkspace.STYLE[self.role]
172
     #     return UserRoleInWorkspace.STYLE[self.role]
171
     #
173
     #
172
 
174
 
175
+    def role_object(self):
176
+        return WorkspaceRoles.get_role_from_level(level=self.role)
177
+
173
     def role_as_label(self):
178
     def role_as_label(self):
174
-        return UserRoleInWorkspace.LABEL[self.role]
179
+        return self.role_object().label
175
 
180
 
176
     @classmethod
181
     @classmethod
177
     def get_all_role_values(cls) -> typing.List[int]:
182
     def get_all_role_values(cls) -> typing.List[int]:
178
         """
183
         """
179
         Return all valid role value
184
         Return all valid role value
180
         """
185
         """
181
-        return [
182
-            UserRoleInWorkspace.READER,
183
-            UserRoleInWorkspace.CONTRIBUTOR,
184
-            UserRoleInWorkspace.CONTENT_MANAGER,
185
-            UserRoleInWorkspace.WORKSPACE_MANAGER
186
-        ]
186
+        return [role.level for role in WorkspaceRoles.get_all_valid_role()]
187
 
187
 
188
     @classmethod
188
     @classmethod
189
     def get_all_role_slug(cls) -> typing.List[str]:
189
     def get_all_role_slug(cls) -> typing.List[str]:
193
         # INFO - G.M - 25-05-2018 - Be carefull, as long as this method
193
         # INFO - G.M - 25-05-2018 - Be carefull, as long as this method
194
         # and get_all_role_values are both used for API, this method should
194
         # and get_all_role_values are both used for API, this method should
195
         # return item in the same order as get_all_role_values
195
         # return item in the same order as get_all_role_values
196
-        return [cls.SLUG[value] for value in cls.get_all_role_values()]
197
-
196
+        return [role.slug for role in WorkspaceRoles.get_all_valid_role()]
198
 
197
 
199
-class RoleType(object):
200
-    def __init__(self, role_id):
201
-        self.role_type_id = role_id
202
-        # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
198
+# TODO - G.M - 10-04-2018 - [Cleanup] Drop this
199
+# class RoleType(object):
200
+#     def __init__(self, role_id):
201
+#         self.role_type_id = role_id
203
         # self.fa_icon = UserRoleInWorkspace.ICON[role_id]
202
         # self.fa_icon = UserRoleInWorkspace.ICON[role_id]
204
         # self.role_label = UserRoleInWorkspace.LABEL[role_id]
203
         # self.role_label = UserRoleInWorkspace.LABEL[role_id]
205
         # self.css_style = UserRoleInWorkspace.STYLE[role_id]
204
         # self.css_style = UserRoleInWorkspace.STYLE[role_id]

+ 61 - 0
tracim/models/roles.py Прегледај датотеку

1
+import typing
2
+from enum import Enum
3
+
4
+from tracim.exceptions import RoleDoesNotExist
5
+
6
+
7
+class WorkspaceRoles(Enum):
8
+    """
9
+    Available role for workspace.
10
+    All roles should have a unique level and unique slug.
11
+    level is role value store in database and is also use for
12
+    permission check.
13
+    slug is for http endpoints and other place where readability is
14
+    needed.
15
+    """
16
+    NOT_APPLICABLE = (0, 'not-applicable')
17
+    READER = (1, 'reader')
18
+    CONTRIBUTOR = (2, 'contributor')
19
+    CONTENT_MANAGER = (4, 'content-manager')
20
+    WORKSPACE_MANAGER = (8, 'workspace-manager')
21
+
22
+    def __init__(self, level, slug):
23
+        self.level = level
24
+        self.slug = slug
25
+    
26
+    @property
27
+    def label(self):
28
+        """ Return valid label associated to role"""
29
+        # TODO - G.M - 2018-06-180 - Make this work correctly
30
+        return self.slug
31
+
32
+    @classmethod
33
+    def get_all_valid_role(cls) -> typing.List['WorkspaceRoles']:
34
+        """
35
+        Return all valid role value
36
+        """
37
+        return [item for item in list(WorkspaceRoles) if item.level > 0]
38
+
39
+    @classmethod
40
+    def get_role_from_level(cls, level: int) -> 'WorkspaceRoles':
41
+        """
42
+        Obtain Workspace role from a level value
43
+        :param level: level value as int
44
+        :return: correct workspace role related
45
+        """
46
+        roles = [item for item in list(WorkspaceRoles) if item.level == level]
47
+        if len(roles) != 1:
48
+            raise RoleDoesNotExist()
49
+        return roles[0]
50
+
51
+    @classmethod
52
+    def get_role_from_slug(cls, slug: str) -> 'WorkspaceRoles':
53
+        """
54
+        Obtain Workspace role from a slug value
55
+        :param slug: slug value as str
56
+        :return: correct workspace role related
57
+        """
58
+        roles = [item for item in list(WorkspaceRoles) if item.slug == slug]
59
+        if len(roles) != 1:
60
+            raise RoleDoesNotExist()
61
+        return roles[0]

+ 1 - 0
tracim/tests/__init__.py Прегледај датотеку

68
             'depot_storage_dir': '/tmp/test/depot',
68
             'depot_storage_dir': '/tmp/test/depot',
69
             'depot_storage_name': 'test',
69
             'depot_storage_name': 'test',
70
             'preview_cache_dir': '/tmp/test/preview_cache',
70
             'preview_cache_dir': '/tmp/test/preview_cache',
71
+            'email.notification.activated': 'false',
71
 
72
 
72
         }
73
         }
73
         hapic.reset_context()
74
         hapic.reset_context()

+ 386 - 1
tracim/tests/functional/test_workspaces.py Прегледај датотеку

92
         assert sidebar_entry['hexcolor'] == "#757575"
92
         assert sidebar_entry['hexcolor'] == "#757575"
93
         assert sidebar_entry['fa_icon'] == "calendar"
93
         assert sidebar_entry['fa_icon'] == "calendar"
94
 
94
 
95
+    def test_api__update_workspace__ok_200__nominal_case(self) -> None:
96
+        """
97
+        Test update workspace
98
+        """
99
+        self.testapp.authorization = (
100
+            'Basic',
101
+            (
102
+                'admin@admin.admin',
103
+                'admin@admin.admin'
104
+            )
105
+        )
106
+        params = {
107
+            'label': 'superworkspace',
108
+            'description': 'mysuperdescription'
109
+        }
110
+        # Before
111
+        res = self.testapp.get(
112
+            '/api/v2/workspaces/1',
113
+            status=200
114
+        )
115
+        assert res.json_body
116
+        workspace = res.json_body
117
+        assert workspace['workspace_id'] == 1
118
+        assert workspace['slug'] == 'business'
119
+        assert workspace['label'] == 'Business'
120
+        assert workspace['description'] == 'All importants documents'
121
+        assert len(workspace['sidebar_entries']) == 7
122
+
123
+        # modify workspace
124
+        res = self.testapp.put_json(
125
+            '/api/v2/workspaces/1',
126
+            status=200,
127
+            params=params,
128
+        )
129
+        assert res.json_body
130
+        workspace = res.json_body
131
+        assert workspace['workspace_id'] == 1
132
+        assert workspace['slug'] == 'superworkspace'
133
+        assert workspace['label'] == 'superworkspace'
134
+        assert workspace['description'] == 'mysuperdescription'
135
+        assert len(workspace['sidebar_entries']) == 7
136
+
137
+        # after
138
+        res = self.testapp.get(
139
+            '/api/v2/workspaces/1',
140
+            status=200
141
+        )
142
+        assert res.json_body
143
+        workspace = res.json_body
144
+        assert workspace['workspace_id'] == 1
145
+        assert workspace['slug'] == 'superworkspace'
146
+        assert workspace['label'] == 'superworkspace'
147
+        assert workspace['description'] == 'mysuperdescription'
148
+        assert len(workspace['sidebar_entries']) == 7
149
+
150
+    def test_api__update_workspace__err_400__empty_label(self) -> None:
151
+        """
152
+        Test update workspace with empty label
153
+        """
154
+        self.testapp.authorization = (
155
+            'Basic',
156
+            (
157
+                'admin@admin.admin',
158
+                'admin@admin.admin'
159
+            )
160
+        )
161
+        params = {
162
+            'label': '',
163
+            'description': 'mysuperdescription'
164
+        }
165
+        res = self.testapp.put_json(
166
+            '/api/v2/workspaces/1',
167
+            status=400,
168
+            params=params,
169
+        )
170
+
171
+    def test_api__create_workspace__ok_200__nominal_case(self) -> None:
172
+        """
173
+        Test create workspace
174
+        """
175
+        self.testapp.authorization = (
176
+            'Basic',
177
+            (
178
+                'admin@admin.admin',
179
+                'admin@admin.admin'
180
+            )
181
+        )
182
+        params = {
183
+            'label': 'superworkspace',
184
+            'description': 'mysuperdescription'
185
+        }
186
+        res = self.testapp.post_json(
187
+            '/api/v2/workspaces',
188
+            status=200,
189
+            params=params,
190
+        )
191
+        assert res.json_body
192
+        workspace = res.json_body
193
+        workspace_id = res.json_body['workspace_id']
194
+        res = self.testapp.get(
195
+            '/api/v2/workspaces/{}'.format(workspace_id),
196
+            status=200
197
+        )
198
+        workspace_2 = res.json_body
199
+        assert workspace == workspace_2
200
+
201
+    def test_api__create_workspace__err_400__empty_label(self) -> None:
202
+        """
203
+        Test create workspace with empty label
204
+        """
205
+        self.testapp.authorization = (
206
+            'Basic',
207
+            (
208
+                'admin@admin.admin',
209
+                'admin@admin.admin'
210
+            )
211
+        )
212
+        params = {
213
+            'label': '',
214
+            'description': 'mysuperdescription'
215
+        }
216
+        res = self.testapp.post_json(
217
+            '/api/v2/workspaces',
218
+            status=400,
219
+            params=params,
220
+        )
221
+
95
     def test_api__get_workspace__err_400__unallowed_user(self) -> None:
222
     def test_api__get_workspace__err_400__unallowed_user(self) -> None:
96
         """
223
         """
97
         Check obtain workspace unreachable for user
224
         Check obtain workspace unreachable for user
168
         assert user_role['role'] == 'workspace-manager'
295
         assert user_role['role'] == 'workspace-manager'
169
         assert user_role['user_id'] == 1
296
         assert user_role['user_id'] == 1
170
         assert user_role['workspace_id'] == 1
297
         assert user_role['workspace_id'] == 1
298
+        assert user_role['workspace']['workspace_id'] == 1
299
+        assert user_role['workspace']['label'] == 'Business'
300
+        assert user_role['workspace']['slug'] == 'business'
171
         assert user_role['user']['public_name'] == 'Global manager'
301
         assert user_role['user']['public_name'] == 'Global manager'
302
+        assert user_role['user']['user_id'] == 1
303
+        assert user_role['is_active'] is True
172
         # TODO - G.M - 24-05-2018 - [Avatar] Replace
304
         # TODO - G.M - 24-05-2018 - [Avatar] Replace
173
         # by correct value when avatar feature will be enabled
305
         # by correct value when avatar feature will be enabled
174
         assert user_role['user']['avatar_url'] is None
306
         assert user_role['user']['avatar_url'] is None
226
         assert 'message' in res.json.keys()
358
         assert 'message' in res.json.keys()
227
         assert 'details' in res.json.keys()
359
         assert 'details' in res.json.keys()
228
 
360
 
361
+    def test_api__create_workspace_member_role__ok_200__user_id(self):
362
+        """
363
+        Create workspace member role
364
+        :return:
365
+        """
366
+        self.testapp.authorization = (
367
+            'Basic',
368
+            (
369
+                'admin@admin.admin',
370
+                'admin@admin.admin'
371
+            )
372
+        )
373
+        # create workspace role
374
+        params = {
375
+            'user_id': 2,
376
+            'user_email_or_public_name': None,
377
+            'role': 'content-manager',
378
+        }
379
+        res = self.testapp.post_json(
380
+            '/api/v2/workspaces/1/members',
381
+            status=200,
382
+            params=params,
383
+        )
384
+        user_role_found = res.json_body
385
+        assert user_role_found['role'] == 'content-manager'
386
+        assert user_role_found['user_id'] == 2
387
+        assert user_role_found['workspace_id'] == 1
388
+        assert user_role_found['newly_created'] is False
389
+        assert user_role_found['email_sent'] is False
390
+
391
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
392
+        assert len(res) == 2
393
+        user_role = res[0]
394
+        assert user_role['role'] == 'workspace-manager'
395
+        assert user_role['user_id'] == 1
396
+        assert user_role['workspace_id'] == 1
397
+        user_role = res[1]
398
+        assert user_role_found['role'] == user_role['role']
399
+        assert user_role_found['user_id'] == user_role['user_id']
400
+        assert user_role_found['workspace_id'] == user_role['workspace_id']
401
+
402
+    def test_api__create_workspace_member_role__ok_200__user_email(self):
403
+        """
404
+        Create workspace member role
405
+        :return:
406
+        """
407
+        self.testapp.authorization = (
408
+            'Basic',
409
+            (
410
+                'admin@admin.admin',
411
+                'admin@admin.admin'
412
+            )
413
+        )
414
+        # create workspace role
415
+        params = {
416
+            'user_id': None,
417
+            'user_email_or_public_name': 'lawrence-not-real-email@fsf.local',
418
+            'role': 'content-manager',
419
+        }
420
+        res = self.testapp.post_json(
421
+            '/api/v2/workspaces/1/members',
422
+            status=200,
423
+            params=params,
424
+        )
425
+        user_role_found = res.json_body
426
+        assert user_role_found['role'] == 'content-manager'
427
+        assert user_role_found['user_id'] == 2
428
+        assert user_role_found['workspace_id'] == 1
429
+        assert user_role_found['newly_created'] is False
430
+        assert user_role_found['email_sent'] is False
431
+
432
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
433
+        assert len(res) == 2
434
+        user_role = res[0]
435
+        assert user_role['role'] == 'workspace-manager'
436
+        assert user_role['user_id'] == 1
437
+        assert user_role['workspace_id'] == 1
438
+        user_role = res[1]
439
+        assert user_role_found['role'] == user_role['role']
440
+        assert user_role_found['user_id'] == user_role['user_id']
441
+        assert user_role_found['workspace_id'] == user_role['workspace_id']
442
+
443
+    def test_api__create_workspace_member_role__ok_200__user_public_name(self):
444
+        """
445
+        Create workspace member role
446
+        :return:
447
+        """
448
+        self.testapp.authorization = (
449
+            'Basic',
450
+            (
451
+                'admin@admin.admin',
452
+                'admin@admin.admin'
453
+            )
454
+        )
455
+        # create workspace role
456
+        params = {
457
+            'user_id': None,
458
+            'user_email_or_public_name': 'Lawrence L.',
459
+            'role': 'content-manager',
460
+        }
461
+        res = self.testapp.post_json(
462
+            '/api/v2/workspaces/1/members',
463
+            status=200,
464
+            params=params,
465
+        )
466
+        user_role_found = res.json_body
467
+        assert user_role_found['role'] == 'content-manager'
468
+        assert user_role_found['user_id'] == 2
469
+        assert user_role_found['workspace_id'] == 1
470
+        assert user_role_found['newly_created'] is False
471
+        assert user_role_found['email_sent'] is False
472
+
473
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
474
+        assert len(res) == 2
475
+        user_role = res[0]
476
+        assert user_role['role'] == 'workspace-manager'
477
+        assert user_role['user_id'] == 1
478
+        assert user_role['workspace_id'] == 1
479
+        user_role = res[1]
480
+        assert user_role_found['role'] == user_role['role']
481
+        assert user_role_found['user_id'] == user_role['user_id']
482
+        assert user_role_found['workspace_id'] == user_role['workspace_id']
483
+
484
+    def test_api__create_workspace_member_role__err_400__nothing(self):
485
+        """
486
+        Create workspace member role
487
+        :return:
488
+        """
489
+        self.testapp.authorization = (
490
+            'Basic',
491
+            (
492
+                'admin@admin.admin',
493
+                'admin@admin.admin'
494
+            )
495
+        )
496
+        # create workspace role
497
+        params = {
498
+            'user_id': None,
499
+            'user_email_or_public_name': None,
500
+            'role': 'content-manager',
501
+        }
502
+        res = self.testapp.post_json(
503
+            '/api/v2/workspaces/1/members',
504
+            status=400,
505
+            params=params,
506
+        )
507
+
508
+    def test_api__create_workspace_member_role__err_400__wrong_user_id(self):
509
+        """
510
+        Create workspace member role
511
+        :return:
512
+        """
513
+        self.testapp.authorization = (
514
+            'Basic',
515
+            (
516
+                'admin@admin.admin',
517
+                'admin@admin.admin'
518
+            )
519
+        )
520
+        # create workspace role
521
+        params = {
522
+            'user_id': 47,
523
+            'user_email_or_public_name': None,
524
+            'role': 'content-manager',
525
+        }
526
+        res = self.testapp.post_json(
527
+            '/api/v2/workspaces/1/members',
528
+            status=400,
529
+            params=params,
530
+        )
531
+
532
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
533
+        """
534
+        Create workspace member role
535
+        :return:
536
+        """
537
+        self.testapp.authorization = (
538
+            'Basic',
539
+            (
540
+                'admin@admin.admin',
541
+                'admin@admin.admin'
542
+            )
543
+        )
544
+        # create workspace role
545
+        params = {
546
+            'user_id': None,
547
+            'user_email_or_public_name': 'nothing@nothing.nothing',
548
+            'role': 'content-manager',
549
+        }
550
+        res = self.testapp.post_json(
551
+            '/api/v2/workspaces/1/members',
552
+            status=200,
553
+            params=params,
554
+        )
555
+        user_role_found = res.json_body
556
+        assert user_role_found['role'] == 'content-manager'
557
+        assert user_role_found['user_id']
558
+        user_id = user_role_found['user_id']
559
+        assert user_role_found['workspace_id'] == 1
560
+        assert user_role_found['newly_created'] is True
561
+        assert user_role_found['email_sent'] is False
562
+
563
+        res = self.testapp.get('/api/v2/workspaces/1/members',
564
+                               status=200).json_body  # nopep8
565
+        assert len(res) == 2
566
+        user_role = res[0]
567
+        assert user_role['role'] == 'workspace-manager'
568
+        assert user_role['user_id'] == 1
569
+        assert user_role['workspace_id'] == 1
570
+        user_role = res[1]
571
+        assert user_role_found['role'] == user_role['role']
572
+        assert user_role_found['user_id'] == user_role['user_id']
573
+        assert user_role_found['workspace_id'] == user_role['workspace_id']
574
+
575
+    def test_api__update_workspace_member_role__ok_200__nominal_case(self):
576
+        """
577
+        Update worskpace member role
578
+        """
579
+        # before
580
+        self.testapp.authorization = (
581
+            'Basic',
582
+            (
583
+                'admin@admin.admin',
584
+                'admin@admin.admin'
585
+            )
586
+        )
587
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
588
+        assert len(res) == 1
589
+        user_role = res[0]
590
+        assert user_role['role'] == 'workspace-manager'
591
+        assert user_role['user_id'] == 1
592
+        assert user_role['workspace_id'] == 1
593
+        # update workspace role
594
+        params = {
595
+            'role': 'content-manager',
596
+        }
597
+        res = self.testapp.put_json(
598
+            '/api/v2/workspaces/1/members/1',
599
+            status=200,
600
+            params=params,
601
+        )
602
+        user_role = res.json_body
603
+        assert user_role['role'] == 'content-manager'
604
+        assert user_role['user_id'] == 1
605
+        assert user_role['workspace_id'] == 1
606
+        # after
607
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
608
+        assert len(res) == 1
609
+        user_role = res[0]
610
+        assert user_role['role'] == 'content-manager'
611
+        assert user_role['user_id'] == 1
612
+        assert user_role['workspace_id'] == 1
613
+
229
 
614
 
230
 class TestWorkspaceContents(FunctionalTest):
615
 class TestWorkspaceContents(FunctionalTest):
231
     """
616
     """
316
         assert content['workspace_id'] == 1
701
         assert content['workspace_id'] == 1
317
 
702
 
318
     # Root related
703
     # Root related
319
-    def test_api__get_workspace_content__ok_200__get_all_root_content__legacy_html_slug(self):
704
+    def test_api__get_workspace_content__ok_200__get_all_root_content__legacy_html_slug(self):  # nopep8
320
         """
705
         """
321
         Check obtain workspace all root contents
706
         Check obtain workspace all root contents
322
         """
707
         """

+ 77 - 0
tracim/tests/library/test_role_api.py Прегледај датотеку

1
+# coding=utf-8
2
+import pytest
3
+from sqlalchemy.orm.exc import NoResultFound
4
+
5
+from tracim.lib.core.userworkspace import RoleApi
6
+from tracim.models import User
7
+from tracim.models.roles import WorkspaceRoles
8
+from tracim.tests import DefaultTest
9
+from tracim.fixtures.users_and_groups import Base as BaseFixture
10
+from tracim.fixtures.content import Content as ContentFixture
11
+
12
+
13
+class TestRoleApi(DefaultTest):
14
+
15
+    fixtures = [BaseFixture, ContentFixture]
16
+
17
+    def test_unit__get_one__ok__nominal_case(self):
18
+        admin = self.session.query(User)\
19
+            .filter(User.email == 'admin@admin.admin').one()
20
+        rapi = RoleApi(
21
+            current_user=admin,
22
+            session=self.session,
23
+            config=self.config,
24
+        )
25
+        rapi.get_one(admin.user_id, 1)
26
+
27
+    def test_unit__get_one__err__role_does_not_exist(self):
28
+        admin = self.session.query(User)\
29
+            .filter(User.email == 'admin@admin.admin').one()
30
+        rapi = RoleApi(
31
+            current_user=admin,
32
+            session=self.session,
33
+            config=self.config,
34
+        )
35
+        with pytest.raises(NoResultFound):
36
+            rapi.get_one(admin.user_id, 100)  # workspace 100 does not exist
37
+
38
+    def test_unit__create_one__nominal_case(self):
39
+        admin = self.session.query(User)\
40
+            .filter(User.email == 'admin@admin.admin').one()
41
+        workspace = self._create_workspace_and_test(
42
+            'workspace_1',
43
+            admin
44
+        )
45
+        bob = self.session.query(User)\
46
+            .filter(User.email == 'bob@fsf.local').one()
47
+        rapi = RoleApi(
48
+            current_user=admin,
49
+            session=self.session,
50
+            config=self.config,
51
+        )
52
+        created_role = rapi.create_one(
53
+            user=bob,
54
+            workspace=workspace,
55
+            role_level=WorkspaceRoles.CONTENT_MANAGER.level,
56
+            with_notif=False,
57
+        )
58
+        obtain_role = rapi.get_one(bob.user_id, workspace.workspace_id)
59
+        assert created_role == obtain_role
60
+
61
+    def test_unit__get_all_for_usages(self):
62
+        admin = self.session.query(User)\
63
+            .filter(User.email == 'admin@admin.admin').one()
64
+        rapi = RoleApi(
65
+            current_user=admin,
66
+            session=self.session,
67
+            config=self.config,
68
+        )
69
+        workspace = self._create_workspace_and_test(
70
+            'workspace_1',
71
+            admin
72
+        )
73
+        roles = rapi.get_all_for_workspace(workspace)
74
+        len(roles) == 1
75
+        roles[0].user_id == admin.user_id
76
+        roles[0].role == WorkspaceRoles.WORKSPACE_MANAGER.level
77
+

+ 1 - 1
tracim/tests/library/test_user_api.py Прегледај датотеку

22
         )
22
         )
23
         u = api.create_minimal_user('bob@bob')
23
         u = api.create_minimal_user('bob@bob')
24
         assert u.email == 'bob@bob'
24
         assert u.email == 'bob@bob'
25
-        assert u.display_name is None
25
+        assert u.display_name == 'bob'
26
 
26
 
27
     def test_unit__create_minimal_user_and_update__ok__nominal_case(self):
27
     def test_unit__create_minimal_user_and_update__ok__nominal_case(self):
28
         api = UserApi(
28
         api = UserApi(

+ 80 - 0
tracim/tests/models/tests_roles.py Прегледај датотеку

1
+# coding=utf-8
2
+import unittest
3
+import pytest
4
+from tracim.exceptions import RoleDoesNotExist
5
+from tracim.models.roles import WorkspaceRoles
6
+
7
+
8
+class TestWorkspacesRoles(unittest.TestCase):
9
+    """
10
+    Test for WorkspaceRoles Enum Object
11
+    """
12
+    def test_workspace_roles__ok__all_list(self):
13
+        roles = list(WorkspaceRoles)
14
+        assert len(roles) == 5
15
+        for role in roles:
16
+            assert role
17
+            assert role.slug
18
+            assert isinstance(role.slug, str)
19
+            assert role.level or role.level == 0
20
+            assert isinstance(role.level, int)
21
+            assert role.label
22
+            assert isinstance(role.slug, str)
23
+        assert WorkspaceRoles['READER']
24
+        assert WorkspaceRoles['NOT_APPLICABLE']
25
+        assert WorkspaceRoles['CONTRIBUTOR']
26
+        assert WorkspaceRoles['WORKSPACE_MANAGER']
27
+        assert WorkspaceRoles['CONTENT_MANAGER']
28
+
29
+    def test__workspace_roles__ok__check_model(self):
30
+        role = WorkspaceRoles.WORKSPACE_MANAGER
31
+        assert role
32
+        assert role.slug
33
+        assert isinstance(role.slug, str)
34
+        assert role.level
35
+        assert isinstance(role.level, int)
36
+        assert role.label
37
+        assert isinstance(role.slug, str)
38
+
39
+    def test_workspace_roles__ok__get_all_valid_roles(self):
40
+        roles = WorkspaceRoles.get_all_valid_role()
41
+        assert len(roles) == 4
42
+        for role in roles:
43
+            assert role
44
+            assert role.slug
45
+            assert isinstance(role.slug, str)
46
+            assert role.level or role.level == 0
47
+            assert isinstance(role.level, int)
48
+            assert role.level > 0
49
+            assert role.label
50
+            assert isinstance(role.slug, str)
51
+
52
+    def test_workspace_roles__ok__get_role__from_level__ok__nominal_case(self):
53
+        role = WorkspaceRoles.get_role_from_level(0)
54
+
55
+        assert role
56
+        assert role.slug
57
+        assert isinstance(role.slug, str)
58
+        assert role.level == 0
59
+        assert isinstance(role.level, int)
60
+        assert role.label
61
+        assert isinstance(role.slug, str)
62
+
63
+    def test_workspace_roles__ok__get_role__from_slug__ok__nominal_case(self):
64
+        role = WorkspaceRoles.get_role_from_slug('reader')
65
+
66
+        assert role
67
+        assert role.slug
68
+        assert isinstance(role.slug, str)
69
+        assert role.level > 0
70
+        assert isinstance(role.level, int)
71
+        assert role.label
72
+        assert isinstance(role.slug, str)
73
+
74
+    def test_workspace_roles__ok__get_role__from_level__err__role_does_not_exist(self):  # nopep8
75
+        with pytest.raises(RoleDoesNotExist):
76
+            WorkspaceRoles.get_role_from_level(-1000)
77
+
78
+    def test_workspace_roles__ok__get_role__from_slug__err__role_does_not_exist(self):  # nopep8
79
+        with pytest.raises(RoleDoesNotExist):
80
+            WorkspaceRoles.get_role_from_slug('this slug does not exist')

+ 82 - 5
tracim/views/core_api/schemas.py Прегледај датотеку

10
 from tracim.models.contents import open_status
10
 from tracim.models.contents import open_status
11
 from tracim.models.contents import ContentTypeLegacy as ContentType
11
 from tracim.models.contents import ContentTypeLegacy as ContentType
12
 from tracim.models.contents import ContentStatusLegacy as ContentStatus
12
 from tracim.models.contents import ContentStatusLegacy as ContentStatus
13
-from tracim.models.context_models import ContentCreation, ActiveContentFilter, \
14
-    ContentIdsQuery
15
-from tracim.models.context_models import UserWorkspacePath
13
+from tracim.models.context_models import ActiveContentFilter
14
+from tracim.models.context_models import ContentIdsQuery
16
 from tracim.models.context_models import UserWorkspaceAndContentPath
15
 from tracim.models.context_models import UserWorkspaceAndContentPath
16
+from tracim.models.context_models import ContentCreation
17
+from tracim.models.context_models import WorkspaceMemberInvitation
18
+from tracim.models.context_models import WorkspaceUpdate
19
+from tracim.models.context_models import RoleUpdate
17
 from tracim.models.context_models import CommentCreation
20
 from tracim.models.context_models import CommentCreation
18
 from tracim.models.context_models import TextBasedContentUpdate
21
 from tracim.models.context_models import TextBasedContentUpdate
19
 from tracim.models.context_models import SetContentStatus
22
 from tracim.models.context_models import SetContentStatus
20
 from tracim.models.context_models import CommentPath
23
 from tracim.models.context_models import CommentPath
21
 from tracim.models.context_models import MoveParams
24
 from tracim.models.context_models import MoveParams
22
 from tracim.models.context_models import WorkspaceAndContentPath
25
 from tracim.models.context_models import WorkspaceAndContentPath
26
+from tracim.models.context_models import WorkspaceAndUserPath
23
 from tracim.models.context_models import ContentFilter
27
 from tracim.models.context_models import ContentFilter
24
 from tracim.models.context_models import LoginCredentials
28
 from tracim.models.context_models import LoginCredentials
25
 from tracim.models.data import UserRoleInWorkspace
29
 from tracim.models.data import UserRoleInWorkspace
110
     )
114
     )
111
 
115
 
112
 
116
 
117
+class WorkspaceAndUserIdPathSchema(
118
+    UserIdPathSchema,
119
+    WorkspaceIdPathSchema
120
+):
121
+    @post_load
122
+    def make_path_object(self, data):
123
+        return WorkspaceAndUserPath(**data)
124
+
125
+
113
 class WorkspaceAndContentIdPathSchema(
126
 class WorkspaceAndContentIdPathSchema(
114
     WorkspaceIdPathSchema,
127
     WorkspaceIdPathSchema,
115
     ContentIdPathSchema
128
     ContentIdPathSchema
135
 ):
148
 ):
136
     @post_load
149
     @post_load
137
     def make_path_object(self, data):
150
     def make_path_object(self, data):
138
-        return UserWorkspacePath(**data)
151
+        return WorkspaceAndUserPath(**data)
139
 
152
 
140
 
153
 
141
 class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
154
 class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
145
         required=True,
158
         required=True,
146
         validate=Range(min=1, error="Value must be greater than 0"),
159
         validate=Range(min=1, error="Value must be greater than 0"),
147
     )
160
     )
161
+
148
     @post_load
162
     @post_load
149
     def make_path_object(self, data):
163
     def make_path_object(self, data):
150
         return CommentPath(**data)
164
         return CommentPath(**data)
228
 ###
242
 ###
229
 
243
 
230
 
244
 
245
+class RoleUpdateSchema(marshmallow.Schema):
246
+    role = marshmallow.fields.String(
247
+        example='contributor',
248
+        validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
249
+    )
250
+
251
+    @post_load
252
+    def make_role(self, data):
253
+        return RoleUpdate(**data)
254
+
255
+
256
+class WorkspaceMemberInviteSchema(RoleUpdateSchema):
257
+    user_id = marshmallow.fields.Int(
258
+        example=5,
259
+        default=None,
260
+        allow_none=True,
261
+    )
262
+    user_email_or_public_name = marshmallow.fields.String(
263
+        example='suri@cate.fr',
264
+        default=None,
265
+        allow_none=True,
266
+    )
267
+
268
+    @post_load
269
+    def make_role(self, data):
270
+        return WorkspaceMemberInvitation(**data)
271
+
272
+
231
 class BasicAuthSchema(marshmallow.Schema):
273
 class BasicAuthSchema(marshmallow.Schema):
232
 
274
 
233
     email = marshmallow.fields.Email(
275
     email = marshmallow.fields.Email(
252
     expire_after = marshmallow.fields.String()
294
     expire_after = marshmallow.fields.String()
253
 
295
 
254
 
296
 
297
+class WorkspaceModifySchema(marshmallow.Schema):
298
+    label = marshmallow.fields.String(
299
+        example='My Workspace',
300
+    )
301
+    description = marshmallow.fields.String(
302
+        example='A super description of my workspace.',
303
+    )
304
+
305
+    @post_load
306
+    def make_workspace_modifications(self, data):
307
+        return WorkspaceUpdate(**data)
308
+
309
+
310
+class WorkspaceCreationSchema(WorkspaceModifySchema):
311
+    pass
312
+
313
+
255
 class NoContentSchema(marshmallow.Schema):
314
 class NoContentSchema(marshmallow.Schema):
256
 
315
 
257
     class Meta:
316
     class Meta:
319
         validate=Range(min=1, error="Value must be greater than 0"),
378
         validate=Range(min=1, error="Value must be greater than 0"),
320
     )
379
     )
321
     user = marshmallow.fields.Nested(
380
     user = marshmallow.fields.Nested(
322
-        UserSchema(only=('public_name', 'avatar_url'))
381
+        UserDigestSchema()
323
     )
382
     )
383
+    workspace = marshmallow.fields.Nested(
384
+        WorkspaceDigestSchema(exclude=('sidebar_entries',))
385
+    )
386
+    is_active = marshmallow.fields.Bool()
324
 
387
 
325
     class Meta:
388
     class Meta:
326
         description = 'Workspace Member information'
389
         description = 'Workspace Member information'
327
 
390
 
328
 
391
 
392
+class WorkspaceMemberCreationSchema(WorkspaceMemberSchema):
393
+    newly_created = marshmallow.fields.Bool(
394
+        exemple=False,
395
+        description='Is the user completely new '
396
+                    '(and account was just created) or not ?',
397
+    )
398
+    email_sent = marshmallow.fields.Bool(
399
+        exemple=False,
400
+        description='Has an email been sent to user to inform him about '
401
+                    'this new workspace registration and eventually his account'
402
+                    'creation'
403
+    )
404
+
405
+
329
 class ApplicationConfigSchema(marshmallow.Schema):
406
 class ApplicationConfigSchema(marshmallow.Schema):
330
     pass
407
     pass
331
     #  TODO - G.M - 24-05-2018 - Set this
408
     #  TODO - G.M - 24-05-2018 - Set this

+ 169 - 7
tracim/views/core_api/workspace_controller.py Прегледај датотеку

1
 import typing
1
 import typing
2
 import transaction
2
 import transaction
3
 from pyramid.config import Configurator
3
 from pyramid.config import Configurator
4
+
5
+from tracim.lib.core.user import UserApi
6
+from tracim.models.roles import WorkspaceRoles
7
+
4
 try:  # Python 3.5+
8
 try:  # Python 3.5+
5
     from http import HTTPStatus
9
     from http import HTTPStatus
6
 except ImportError:
10
 except ImportError:
12
 from tracim.lib.core.content import ContentApi
16
 from tracim.lib.core.content import ContentApi
13
 from tracim.lib.core.userworkspace import RoleApi
17
 from tracim.lib.core.userworkspace import RoleApi
14
 from tracim.lib.utils.authorization import require_workspace_role
18
 from tracim.lib.utils.authorization import require_workspace_role
19
+from tracim.lib.utils.authorization import require_profile
20
+from tracim.models import Group
15
 from tracim.lib.utils.authorization import require_candidate_workspace_role
21
 from tracim.lib.utils.authorization import require_candidate_workspace_role
16
 from tracim.models.data import UserRoleInWorkspace
22
 from tracim.models.data import UserRoleInWorkspace
17
 from tracim.models.data import ActionDescription
23
 from tracim.models.data import ActionDescription
18
 from tracim.models.context_models import UserRoleWorkspaceInContext
24
 from tracim.models.context_models import UserRoleWorkspaceInContext
19
 from tracim.models.context_models import ContentInContext
25
 from tracim.models.context_models import ContentInContext
20
 from tracim.exceptions import EmptyLabelNotAllowed
26
 from tracim.exceptions import EmptyLabelNotAllowed
27
+from tracim.exceptions import EmailValidationFailed
28
+from tracim.exceptions import UserCreationFailed
29
+from tracim.exceptions import UserDoesNotExist
21
 from tracim.exceptions import ContentNotFound
30
 from tracim.exceptions import ContentNotFound
22
 from tracim.exceptions import WorkspacesDoNotMatch
31
 from tracim.exceptions import WorkspacesDoNotMatch
23
 from tracim.exceptions import ParentNotFound
32
 from tracim.exceptions import ParentNotFound
24
 from tracim.views.controllers import Controller
33
 from tracim.views.controllers import Controller
25
 from tracim.views.core_api.schemas import FilterContentQuerySchema
34
 from tracim.views.core_api.schemas import FilterContentQuerySchema
35
+from tracim.views.core_api.schemas import WorkspaceMemberCreationSchema
36
+from tracim.views.core_api.schemas import WorkspaceMemberInviteSchema
37
+from tracim.views.core_api.schemas import RoleUpdateSchema
38
+from tracim.views.core_api.schemas import WorkspaceCreationSchema
39
+from tracim.views.core_api.schemas import WorkspaceModifySchema
40
+from tracim.views.core_api.schemas import WorkspaceAndUserIdPathSchema
26
 from tracim.views.core_api.schemas import ContentMoveSchema
41
 from tracim.views.core_api.schemas import ContentMoveSchema
27
 from tracim.views.core_api.schemas import NoContentSchema
42
 from tracim.views.core_api.schemas import NoContentSchema
28
 from tracim.views.core_api.schemas import ContentCreationSchema
43
 from tracim.views.core_api.schemas import ContentCreationSchema
47
         """
62
         """
48
         Get workspace informations
63
         Get workspace informations
49
         """
64
         """
50
-        wid = hapic_data.path['workspace_id']
51
         app_config = request.registry.settings['CFG']
65
         app_config = request.registry.settings['CFG']
52
         wapi = WorkspaceApi(
66
         wapi = WorkspaceApi(
53
             current_user=request.current_user,  # User
67
             current_user=request.current_user,  # User
57
         return wapi.get_workspace_with_context(request.current_workspace)
71
         return wapi.get_workspace_with_context(request.current_workspace)
58
 
72
 
59
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
73
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
74
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
75
+    @require_workspace_role(UserRoleInWorkspace.WORKSPACE_MANAGER)
76
+    @hapic.input_path(WorkspaceIdPathSchema())
77
+    @hapic.input_body(WorkspaceModifySchema())
78
+    @hapic.output_body(WorkspaceSchema())
79
+    def update_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
80
+        """
81
+        Update workspace informations
82
+        """
83
+        app_config = request.registry.settings['CFG']
84
+        wapi = WorkspaceApi(
85
+            current_user=request.current_user,  # User
86
+            session=request.dbsession,
87
+            config=app_config,
88
+        )
89
+        wapi.update_workspace(
90
+            request.current_workspace,
91
+            label=hapic_data.body.label,
92
+            description=hapic_data.body.description,
93
+            save_now=True,
94
+        )
95
+        return wapi.get_workspace_with_context(request.current_workspace)
96
+
97
+    @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
98
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
99
+    @require_profile(Group.TIM_MANAGER)
100
+    @hapic.input_body(WorkspaceCreationSchema())
101
+    @hapic.output_body(WorkspaceSchema())
102
+    def create_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
103
+        """
104
+        create workspace
105
+        """
106
+        app_config = request.registry.settings['CFG']
107
+        wapi = WorkspaceApi(
108
+            current_user=request.current_user,  # User
109
+            session=request.dbsession,
110
+            config=app_config,
111
+        )
112
+        workspace = wapi.create_workspace(
113
+            label=hapic_data.body.label,
114
+            description=hapic_data.body.description,
115
+            save_now=True,
116
+        )
117
+        return wapi.get_workspace_with_context(workspace)
118
+
119
+    @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
60
     @require_workspace_role(UserRoleInWorkspace.READER)
120
     @require_workspace_role(UserRoleInWorkspace.READER)
61
     @hapic.input_path(WorkspaceIdPathSchema())
121
     @hapic.input_path(WorkspaceIdPathSchema())
62
     @hapic.output_body(WorkspaceMemberSchema(many=True))
122
     @hapic.output_body(WorkspaceMemberSchema(many=True))
83
         ]
143
         ]
84
 
144
 
85
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
145
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
146
+    @require_workspace_role(UserRoleInWorkspace.WORKSPACE_MANAGER)
147
+    @hapic.input_path(WorkspaceAndUserIdPathSchema())
148
+    @hapic.input_body(RoleUpdateSchema())
149
+    @hapic.output_body(WorkspaceMemberSchema())
150
+    def update_workspaces_members_role(
151
+            self,
152
+            context,
153
+            request: TracimRequest,
154
+            hapic_data=None
155
+    ) -> UserRoleWorkspaceInContext:
156
+        """
157
+        Update Members to this workspace
158
+        """
159
+        app_config = request.registry.settings['CFG']
160
+        rapi = RoleApi(
161
+            current_user=request.current_user,
162
+            session=request.dbsession,
163
+            config=app_config,
164
+        )
165
+
166
+        role = rapi.get_one(
167
+            user_id=hapic_data.path.user_id,
168
+            workspace_id=hapic_data.path.workspace_id,
169
+        )
170
+        workspace_role = WorkspaceRoles.get_role_from_slug(hapic_data.body.role)
171
+        role = rapi.update_role(
172
+            role,
173
+            role_level=workspace_role.level
174
+        )
175
+        return rapi.get_user_role_workspace_with_context(role)
176
+
177
+    @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
178
+    @hapic.handle_exception(UserCreationFailed, HTTPStatus.BAD_REQUEST)
179
+    @require_workspace_role(UserRoleInWorkspace.WORKSPACE_MANAGER)
180
+    @hapic.input_path(WorkspaceIdPathSchema())
181
+    @hapic.input_body(WorkspaceMemberInviteSchema())
182
+    @hapic.output_body(WorkspaceMemberCreationSchema())
183
+    def create_workspaces_members_role(
184
+            self,
185
+            context,
186
+            request: TracimRequest,
187
+            hapic_data=None
188
+    ) -> UserRoleWorkspaceInContext:
189
+        """
190
+        Add Members to this workspace
191
+        """
192
+        newly_created = False
193
+        email_sent = False
194
+        app_config = request.registry.settings['CFG']
195
+        rapi = RoleApi(
196
+            current_user=request.current_user,
197
+            session=request.dbsession,
198
+            config=app_config,
199
+        )
200
+        uapi = UserApi(
201
+            current_user=request.current_user,
202
+            session=request.dbsession,
203
+            config=app_config,
204
+        )
205
+        try:
206
+            _, user = uapi.find(
207
+                user_id=hapic_data.body.user_id,
208
+                email=hapic_data.body.user_email_or_public_name,
209
+                public_name=hapic_data.body.user_email_or_public_name
210
+            )
211
+        except UserDoesNotExist:
212
+            try:
213
+                # TODO - G.M - 2018-07-05 - [UserCreation] Reenable email
214
+                # notification for creation
215
+                user = uapi.create_user(
216
+                    hapic_data.body.user_email_or_public_name,
217
+                    do_notify=False
218
+                )  # nopep8
219
+                newly_created = True
220
+            except EmailValidationFailed:
221
+                raise UserCreationFailed('no valid mail given')
222
+        role = rapi.create_one(
223
+            user=user,
224
+            workspace=request.current_workspace,
225
+            role_level=WorkspaceRoles.get_role_from_slug(hapic_data.body.role).level,  # nopep8
226
+            with_notif=False,
227
+            flush=True,
228
+        )
229
+        return rapi.get_user_role_workspace_with_context(
230
+            role,
231
+            newly_created=newly_created,
232
+            email_sent=email_sent,
233
+        )
234
+
235
+    @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
86
     @require_workspace_role(UserRoleInWorkspace.READER)
236
     @require_workspace_role(UserRoleInWorkspace.READER)
87
     @hapic.input_path(WorkspaceIdPathSchema())
237
     @hapic.input_path(WorkspaceIdPathSchema())
88
     @hapic.input_query(FilterContentQuerySchema())
238
     @hapic.input_query(FilterContentQuerySchema())
168
             context,
318
             context,
169
             request: TracimRequest,
319
             request: TracimRequest,
170
             hapic_data=None,
320
             hapic_data=None,
171
-    ) -> typing.List[ContentInContext]:
321
+    ) -> ContentInContext:
172
         """
322
         """
173
         move a content
323
         move a content
174
         """
324
         """
217
             context,
367
             context,
218
             request: TracimRequest,
368
             request: TracimRequest,
219
             hapic_data=None,
369
             hapic_data=None,
220
-    ) -> typing.List[ContentInContext]:
370
+    ) -> None:
221
         """
371
         """
222
         delete a content
372
         delete a content
223
         """
373
         """
249
             context,
399
             context,
250
             request: TracimRequest,
400
             request: TracimRequest,
251
             hapic_data=None,
401
             hapic_data=None,
252
-    ) -> typing.List[ContentInContext]:
402
+    ) -> None:
253
         """
403
         """
254
         undelete a content
404
         undelete a content
255
         """
405
         """
282
             context,
432
             context,
283
             request: TracimRequest,
433
             request: TracimRequest,
284
             hapic_data=None,
434
             hapic_data=None,
285
-    ) -> typing.List[ContentInContext]:
435
+    ) -> None:
286
         """
436
         """
287
         archive a content
437
         archive a content
288
         """
438
         """
293
             session=request.dbsession,
443
             session=request.dbsession,
294
             config=app_config,
444
             config=app_config,
295
         )
445
         )
296
-        content = api.get_one(path_data.content_id, content_type=ContentType.Any)
446
+        content = api.get_one(path_data.content_id, content_type=ContentType.Any)  # nopep8
297
         with new_revision(
447
         with new_revision(
298
                 session=request.dbsession,
448
                 session=request.dbsession,
299
                 tm=transaction.manager,
449
                 tm=transaction.manager,
311
             context,
461
             context,
312
             request: TracimRequest,
462
             request: TracimRequest,
313
             hapic_data=None,
463
             hapic_data=None,
314
-    ) -> typing.List[ContentInContext]:
464
+    ) -> None:
315
         """
465
         """
316
         unarchive a content
466
         unarchive a content
317
         """
467
         """
344
         # Workspace
494
         # Workspace
345
         configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
495
         configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
346
         configurator.add_view(self.workspace, route_name='workspace')
496
         configurator.add_view(self.workspace, route_name='workspace')
497
+        # Create workspace
498
+        configurator.add_route('create_workspace', '/workspaces', request_method='POST')  # nopep8
499
+        configurator.add_view(self.create_workspace, route_name='create_workspace')  # nopep8
500
+        # Update Workspace
501
+        configurator.add_route('update_workspace', '/workspaces/{workspace_id}', request_method='PUT')  # nopep8
502
+        configurator.add_view(self.update_workspace, route_name='update_workspace')  # nopep8
347
         # Workspace Members (Roles)
503
         # Workspace Members (Roles)
348
         configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
504
         configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
349
         configurator.add_view(self.workspaces_members, route_name='workspace_members')  # nopep8
505
         configurator.add_view(self.workspaces_members, route_name='workspace_members')  # nopep8
506
+        # Update Workspace Members roles
507
+        configurator.add_route('update_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='PUT')  # nopep8
508
+        configurator.add_view(self.update_workspaces_members_role, route_name='update_workspace_member')  # nopep8
509
+        # Create Workspace Members roles
510
+        configurator.add_route('create_workspace_member', '/workspaces/{workspace_id}/members', request_method='POST')  # nopep8
511
+        configurator.add_view(self.create_workspaces_members_role, route_name='create_workspace_member')  # nopep8
350
         # Workspace Content
512
         # Workspace Content
351
         configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET')  # nopep8
513
         configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET')  # nopep8
352
         configurator.add_view(self.workspace_content, route_name='workspace_content')  # nopep8
514
         configurator.add_view(self.workspace_content, route_name='workspace_content')  # nopep8