Преглед на файлове

Merge pull request #93 from tracim/fix/better_content_types_and_status_code

Damien Accorsi преди 6 години
родител
ревизия
60e88c581c
No account linked to committer's email

+ 38 - 6
tracim/__init__.py Целия файл

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
-import json
3
-import time
2
+try:  # Python 3.5+
3
+    from http import HTTPStatus
4
+except ImportError:
5
+    from http import client as HTTPStatus
4
 
6
 
5
 from pyramid.config import Configurator
7
 from pyramid.config import Configurator
6
 from pyramid.authentication import BasicAuthAuthenticationPolicy
8
 from pyramid.authentication import BasicAuthAuthenticationPolicy
15
 from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
17
 from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
16
 from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
18
 from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
17
 from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
19
 from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
20
+from tracim.lib.utils.cors import add_cors_support
18
 from tracim.lib.webdav import WebdavAppFactory
21
 from tracim.lib.webdav import WebdavAppFactory
19
 from tracim.views import BASE_API_V2
22
 from tracim.views import BASE_API_V2
20
 from tracim.views.contents_api.html_document_controller import HTMLDocumentController  # nopep8
23
 from tracim.views.contents_api.html_document_controller import HTMLDocumentController  # nopep8
25
 from tracim.views.core_api.workspace_controller import WorkspaceController
28
 from tracim.views.core_api.workspace_controller import WorkspaceController
26
 from tracim.views.contents_api.comment_controller import CommentController
29
 from tracim.views.contents_api.comment_controller import CommentController
27
 from tracim.views.errors import ErrorSchema
30
 from tracim.views.errors import ErrorSchema
28
-from tracim.lib.utils.cors import add_cors_support
31
+from tracim.exceptions import NotAuthenticated
32
+from tracim.exceptions import InvalidId
33
+from tracim.exceptions import InsufficientUserProfile
34
+from tracim.exceptions import InsufficientUserRoleInWorkspace
35
+from tracim.exceptions import WorkspaceNotFoundInTracimRequest
36
+from tracim.exceptions import UserNotFoundInTracimRequest
37
+from tracim.exceptions import ContentNotFoundInTracimRequest
38
+from tracim.exceptions import WorkspaceNotFound
39
+from tracim.exceptions import ContentNotFound
40
+from tracim.exceptions import UserDoesNotExist
41
+from tracim.exceptions import AuthenticationFailed
42
+from tracim.exceptions import ContentTypeNotAllowed
29
 
43
 
30
 
44
 
31
 def web(global_config, **local_settings):
45
 def web(global_config, **local_settings):
66
         debug=app_config.DEBUG,
80
         debug=app_config.DEBUG,
67
     )
81
     )
68
     hapic.set_context(context)
82
     hapic.set_context(context)
69
-    context.handle_exception(NotFound, 404)
70
-    context.handle_exception(OperationalError, 500)
71
-    context.handle_exception(Exception, 500)
83
+    # INFO - G.M - 2018-07-04 - global-context exceptions
84
+    # Not found
85
+    context.handle_exception(NotFound, HTTPStatus.NOT_FOUND)
86
+    # Bad request
87
+    context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
88
+    context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
89
+    context.handle_exception(ContentNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
90
+    context.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
91
+    context.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
92
+    context.handle_exception(ContentNotFound, HTTPStatus.BAD_REQUEST)
93
+    context.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
94
+    context.handle_exception(InvalidId, HTTPStatus.BAD_REQUEST)
95
+    # Auth exception
96
+    context.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
97
+    context.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
98
+    context.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)  # nopep8
99
+    context.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
100
+    # Internal server error
101
+    context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
102
+    context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)
103
+
72
     # Add controllers
104
     # Add controllers
73
     session_controller = SessionController()
105
     session_controller = SessionController()
74
     system_controller = SystemController()
106
     system_controller = SystemController()

+ 31 - 0
tracim/exceptions.py Целия файл

121
     pass
121
     pass
122
 
122
 
123
 
123
 
124
+class InvalidId(TracimException):
125
+    pass
126
+
127
+
128
+class InvalidContentId(InvalidId):
129
+    pass
130
+
131
+
132
+class InvalidCommentId(InvalidId):
133
+    pass
134
+
135
+
136
+class InvalidWorkspaceId(InvalidId):
137
+    pass
138
+
139
+
140
+class InvalidUserId(InvalidId):
141
+    pass
142
+
124
 class ContentNotFound(TracimException):
143
 class ContentNotFound(TracimException):
125
     pass
144
     pass
126
 
145
 
131
 
150
 
132
 class WorkspacesDoNotMatch(TracimException):
151
 class WorkspacesDoNotMatch(TracimException):
133
     pass
152
     pass
153
+
154
+
155
+class EmptyValueNotAllowed(TracimException):
156
+    pass
157
+
158
+
159
+class EmptyLabelNotAllowed(EmptyValueNotAllowed):
160
+    pass
161
+
162
+
163
+class EmptyCommentContentNotAllowed(EmptyValueNotAllowed):
164
+    pass

+ 31 - 7
tracim/lib/core/content.py Целия файл

5
 import re
5
 import re
6
 import typing
6
 import typing
7
 from operator import itemgetter
7
 from operator import itemgetter
8
-from operator import not_
9
 
8
 
10
 import transaction
9
 import transaction
11
 from sqlalchemy import func
10
 from sqlalchemy import func
26
 from tracim.lib.utils.utils import cmp_to_key
25
 from tracim.lib.utils.utils import cmp_to_key
27
 from tracim.lib.core.notifications import NotifierFactory
26
 from tracim.lib.core.notifications import NotifierFactory
28
 from tracim.exceptions import SameValueError
27
 from tracim.exceptions import SameValueError
28
+from tracim.exceptions import EmptyCommentContentNotAllowed
29
+from tracim.exceptions import EmptyLabelNotAllowed
29
 from tracim.exceptions import ContentNotFound
30
 from tracim.exceptions import ContentNotFound
30
 from tracim.exceptions import WorkspacesDoNotMatch
31
 from tracim.exceptions import WorkspacesDoNotMatch
31
 from tracim.lib.utils.utils import current_date_for_filename
32
 from tracim.lib.utils.utils import current_date_for_filename
393
 
394
 
394
         return result
395
         return result
395
 
396
 
396
-    def create(self, content_type: str, workspace: Workspace, parent: Content=None, label:str ='', do_save=False, is_temporary: bool=False, do_notify=True) -> Content:
397
+    def create(self, content_type: str, workspace: Workspace, parent: Content=None, label: str ='', filename: str = '', do_save=False, is_temporary: bool=False, do_notify=True) -> Content:
398
+        # TODO - G.M - 2018-07-16 - raise Exception instead of assert
397
         assert content_type in ContentType.allowed_types()
399
         assert content_type in ContentType.allowed_types()
400
+        assert not (label and filename)
398
 
401
 
399
         if content_type == ContentType.Folder and not label:
402
         if content_type == ContentType.Folder and not label:
400
             label = self.generate_folder_label(workspace, parent)
403
             label = self.generate_folder_label(workspace, parent)
401
 
404
 
402
         content = Content()
405
         content = Content()
406
+
407
+        if filename:
408
+            # INFO - G.M - 2018-07-04 - File_name setting automatically
409
+            # set label and file_extension
410
+            content.file_name = label
411
+        elif label:
412
+            content.label = label
413
+        else:
414
+            if content_type == ContentType.Comment:
415
+                # INFO - G.M - 2018-07-16 - Default label for comments is
416
+                # empty string.
417
+                content.label = ''
418
+            else:
419
+                raise EmptyLabelNotAllowed('Content of this type should have a valid label')  # nopep8
420
+
403
         content.owner = self._user
421
         content.owner = self._user
404
         content.parent = parent
422
         content.parent = parent
405
         content.workspace = workspace
423
         content.workspace = workspace
406
         content.type = content_type
424
         content.type = content_type
407
-        content.label = label
408
         content.is_temporary = is_temporary
425
         content.is_temporary = is_temporary
409
         content.revision_type = ActionDescription.CREATION
426
         content.revision_type = ActionDescription.CREATION
410
 
427
 
419
             self.save(content, ActionDescription.CREATION, do_notify=do_notify)
436
             self.save(content, ActionDescription.CREATION, do_notify=do_notify)
420
         return content
437
         return content
421
 
438
 
422
-
423
     def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
439
     def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
424
-        assert parent  and parent.type!=ContentType.Folder
440
+        assert parent and parent.type != ContentType.Folder
441
+        if not content:
442
+            raise EmptyCommentContentNotAllowed()
425
         item = Content()
443
         item = Content()
426
         item.owner = self._user
444
         item.owner = self._user
427
         item.parent = parent
445
         item.parent = parent
428
-        if parent and not workspace:
446
+        if not workspace:
429
             workspace = item.parent.workspace
447
             workspace = item.parent.workspace
430
         item.workspace = workspace
448
         item.workspace = workspace
431
         item.type = ContentType.Comment
449
         item.type = ContentType.Comment
437
             self.save(item, ActionDescription.COMMENT)
455
             self.save(item, ActionDescription.COMMENT)
438
         return item
456
         return item
439
 
457
 
440
-
441
     def get_one_from_revision(self, content_id: int, content_type: str, workspace: Workspace=None, revision_id=None) -> Content:
458
     def get_one_from_revision(self, content_id: int, content_type: str, workspace: Workspace=None, revision_id=None) -> Content:
442
         """
459
         """
443
         This method is a hack to convert a node revision item into a node
460
         This method is a hack to convert a node revision item into a node
474
         try:
491
         try:
475
             content = base_request.one()
492
             content = base_request.one()
476
         except NoResultFound as exc:
493
         except NoResultFound as exc:
494
+            # TODO - G.M - 2018-07-16 - Add better support for all different
495
+            # error case who can happened here
496
+            # like content doesn't exist, wrong parent, wrong content_type, wrong workspace,
497
+            # wrong access to this workspace, wrong base filter according
498
+            # to content_status.
477
             raise ContentNotFound('Content "{}" not found in database'.format(content_id)) from exc  # nopep8
499
             raise ContentNotFound('Content "{}" not found in database'.format(content_id)) from exc  # nopep8
478
         return content
500
         return content
479
 
501
 
972
             # TODO - G.M - 20-03-2018 - Fix internatization for webdav access.
994
             # TODO - G.M - 20-03-2018 - Fix internatization for webdav access.
973
             # Internatization disabled in libcontent for now.
995
             # Internatization disabled in libcontent for now.
974
             raise SameValueError('The content did not changed')
996
             raise SameValueError('The content did not changed')
997
+        if not new_label:
998
+            raise EmptyLabelNotAllowed()
975
         item.owner = self._user
999
         item.owner = self._user
976
         item.label = new_label
1000
         item.label = new_label
977
         item.description = new_content if new_content else item.description # TODO: convert urls into links
1001
         item.description = new_content if new_content else item.description # TODO: convert urls into links

+ 25 - 9
tracim/lib/utils/request.py Целия файл

2
 from pyramid.request import Request
2
 from pyramid.request import Request
3
 from sqlalchemy.orm.exc import NoResultFound
3
 from sqlalchemy.orm.exc import NoResultFound
4
 
4
 
5
-from tracim.exceptions import NotAuthenticated, ContentNotFound
5
+from tracim.exceptions import NotAuthenticated
6
+from tracim.exceptions import ContentNotFound
7
+from tracim.exceptions import InvalidUserId
8
+from tracim.exceptions import InvalidWorkspaceId
9
+from tracim.exceptions import InvalidContentId
10
+from tracim.exceptions import InvalidCommentId
6
 from tracim.exceptions import ContentNotFoundInTracimRequest
11
 from tracim.exceptions import ContentNotFoundInTracimRequest
7
 from tracim.exceptions import WorkspaceNotFoundInTracimRequest
12
 from tracim.exceptions import WorkspaceNotFoundInTracimRequest
8
 from tracim.exceptions import UserNotFoundInTracimRequest
13
 from tracim.exceptions import UserNotFoundInTracimRequest
214
         comment_id = ''
219
         comment_id = ''
215
         try:
220
         try:
216
             if 'comment_id' in request.matchdict:
221
             if 'comment_id' in request.matchdict:
222
+                comment_id_str = request.matchdict['content_id']
223
+                if not isinstance(comment_id_str, str) or not comment_id_str.isdecimal():  # nopep8
224
+                    raise InvalidCommentId('comment_id is not a correct integer')  # nopep8
217
                 comment_id = int(request.matchdict['comment_id'])
225
                 comment_id = int(request.matchdict['comment_id'])
218
             if not comment_id:
226
             if not comment_id:
219
                 raise ContentNotFoundInTracimRequest('No comment_id property found in request')  # nopep8
227
                 raise ContentNotFoundInTracimRequest('No comment_id property found in request')  # nopep8
228
                 workspace=workspace,
236
                 workspace=workspace,
229
                 parent=content,
237
                 parent=content,
230
             )
238
             )
231
-        except JSONDecodeError as exc:
232
-            raise ContentNotFound('Invalid JSON content') from exc
233
         except NoResultFound as exc:
239
         except NoResultFound as exc:
234
             raise ContentNotFound(
240
             raise ContentNotFound(
235
                 'Comment {} does not exist '
241
                 'Comment {} does not exist '
253
         content_id = ''
259
         content_id = ''
254
         try:
260
         try:
255
             if 'content_id' in request.matchdict:
261
             if 'content_id' in request.matchdict:
262
+                content_id_str = request.matchdict['content_id']
263
+                if not isinstance(content_id_str, str) or not content_id_str.isdecimal():  # nopep8
264
+                    raise InvalidContentId('content_id is not a correct integer')  # nopep8
256
                 content_id = int(request.matchdict['content_id'])
265
                 content_id = int(request.matchdict['content_id'])
257
             if not content_id:
266
             if not content_id:
258
                 raise ContentNotFoundInTracimRequest('No content_id property found in request')  # nopep8
267
                 raise ContentNotFoundInTracimRequest('No content_id property found in request')  # nopep8
262
                 config=request.registry.settings['CFG']
271
                 config=request.registry.settings['CFG']
263
             )
272
             )
264
             content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any)  # nopep8
273
             content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any)  # nopep8
265
-        except JSONDecodeError as exc:
266
-            raise ContentNotFound('Invalid JSON content') from exc
267
         except NoResultFound as exc:
274
         except NoResultFound as exc:
268
             raise ContentNotFound(
275
             raise ContentNotFound(
269
                 'Content {} does not exist '
276
                 'Content {} does not exist '
286
         try:
293
         try:
287
             login = None
294
             login = None
288
             if 'user_id' in request.matchdict:
295
             if 'user_id' in request.matchdict:
289
-                login = request.matchdict['user_id']
296
+                user_id_str = request.matchdict['user_id']
297
+                if not isinstance(user_id_str, str) or not user_id_str.isdecimal():
298
+                    raise InvalidUserId('user_id is not a correct integer')  # nopep8
299
+                login = int(request.matchdict['user_id'])
290
             if not login:
300
             if not login:
291
                 raise UserNotFoundInTracimRequest('You request a candidate user but the context not permit to found one')  # nopep8
301
                 raise UserNotFoundInTracimRequest('You request a candidate user but the context not permit to found one')  # nopep8
292
             user = uapi.get_one(login)
302
             user = uapi.get_one(login)
329
         workspace_id = ''
339
         workspace_id = ''
330
         try:
340
         try:
331
             if 'workspace_id' in request.matchdict:
341
             if 'workspace_id' in request.matchdict:
332
-                workspace_id = request.matchdict['workspace_id']
342
+                workspace_id_str = request.matchdict['workspace_id']
343
+                if not isinstance(workspace_id_str, str) or not workspace_id_str.isdecimal():  # nopep8
344
+                    raise InvalidWorkspaceId('workspace_id is not a correct integer')  # nopep8
345
+                workspace_id = int(request.matchdict['workspace_id'])
333
             if not workspace_id:
346
             if not workspace_id:
334
                 raise WorkspaceNotFoundInTracimRequest('No workspace_id property found in request')  # nopep8
347
                 raise WorkspaceNotFoundInTracimRequest('No workspace_id property found in request')  # nopep8
335
             wapi = WorkspaceApi(
348
             wapi = WorkspaceApi(
338
                 config=request.registry.settings['CFG']
351
                 config=request.registry.settings['CFG']
339
             )
352
             )
340
             workspace = wapi.get_one(workspace_id)
353
             workspace = wapi.get_one(workspace_id)
341
-        except JSONDecodeError as exc:
342
-            raise WorkspaceNotFound('Invalid JSON content') from exc
343
         except NoResultFound as exc:
354
         except NoResultFound as exc:
344
             raise WorkspaceNotFound(
355
             raise WorkspaceNotFound(
345
                 'Workspace {} does not exist '
356
                 'Workspace {} does not exist '
362
         try:
373
         try:
363
             if 'new_workspace_id' in request.json_body:
374
             if 'new_workspace_id' in request.json_body:
364
                 workspace_id = request.json_body['new_workspace_id']
375
                 workspace_id = request.json_body['new_workspace_id']
376
+                if not isinstance(workspace_id, int):
377
+                    if workspace_id.isdecimal():
378
+                        workspace_id = int(workspace_id)
379
+                    else:
380
+                        raise InvalidWorkspaceId('workspace_id is not a correct integer')  # nopep8
365
             if not workspace_id:
381
             if not workspace_id:
366
                 raise WorkspaceNotFoundInTracimRequest('No new_workspace_id property found in body')  # nopep8
382
                 raise WorkspaceNotFoundInTracimRequest('No new_workspace_id property found in body')  # nopep8
367
             wapi = WorkspaceApi(
383
             wapi = WorkspaceApi(

+ 1 - 0
tracim/lib/webdav/utils.py Целия файл

176
         is_temporary = self._file_name.startswith('.~') or self._file_name.startswith('~')
176
         is_temporary = self._file_name.startswith('.~') or self._file_name.startswith('~')
177
 
177
 
178
         file = self._api.create(
178
         file = self._api.create(
179
+            filename=self._file_name,
179
             content_type=ContentType.File,
180
             content_type=ContentType.File,
180
             workspace=self._workspace,
181
             workspace=self._workspace,
181
             parent=self._parent,
182
             parent=self._parent,

+ 2 - 23
tracim/models/context_models.py Целия файл

110
         self.status = status
110
         self.status = status
111
 
111
 
112
 
112
 
113
-class HTMLDocumentUpdate(object):
113
+class TextBasedContentUpdate(object):
114
     """
114
     """
115
-    Html Document update model
116
-    """
117
-    def __init__(
118
-            self,
119
-            label: str,
120
-            raw_content: str,
121
-    ) -> None:
122
-        self.label = label
123
-        self.raw_content = raw_content
124
-
125
-
126
-class ThreadUpdate(object):
127
-    """
128
-    Thread update model
115
+    TextBasedContent update model
129
     """
116
     """
130
     def __init__(
117
     def __init__(
131
             self,
118
             self,
351
         return self.content.content_id
338
         return self.content.content_id
352
 
339
 
353
     @property
340
     @property
354
-    def id(self) -> int:
355
-        return self.content_id
356
-
357
-    @property
358
     def parent_id(self) -> int:
341
     def parent_id(self) -> int:
359
         """
342
         """
360
         Return parent_id of the content
343
         Return parent_id of the content
458
         return self.revision.content_id
441
         return self.revision.content_id
459
 
442
 
460
     @property
443
     @property
461
-    def id(self) -> int:
462
-        return self.content_id
463
-
464
-    @property
465
     def parent_id(self) -> int:
444
     def parent_id(self) -> int:
466
         """
445
         """
467
         Return parent_id of the content
446
         Return parent_id of the content

+ 20 - 0
tracim/tests/functional/test_comments.py Целия файл

94
         assert len(res.json_body) == 4
94
         assert len(res.json_body) == 4
95
         assert comment == res.json_body[3]
95
         assert comment == res.json_body[3]
96
 
96
 
97
+    def test_api__post_content_comment__err_400__empty_raw_content(self) -> None:
98
+        """
99
+        Get alls comments of a content
100
+        """
101
+        self.testapp.authorization = (
102
+            'Basic',
103
+            (
104
+                'admin@admin.admin',
105
+                'admin@admin.admin'
106
+            )
107
+        )
108
+        params = {
109
+            'raw_content': ''
110
+        }
111
+        res = self.testapp.post_json(
112
+            '/api/v2/workspaces/2/contents/7/comments',
113
+            params=params,
114
+            status=400
115
+        )
116
+
97
     def test_api__delete_content_comment__ok_200__user_is_owner_and_workspace_manager(self) -> None:  # nopep8
117
     def test_api__delete_content_comment__ok_200__user_is_owner_and_workspace_manager(self) -> None:  # nopep8
98
         """
118
         """
99
         delete comment (user is workspace_manager and owner)
119
         delete comment (user is workspace_manager and owner)

+ 268 - 25
tracim/tests/functional/test_contents.py Целия файл

13
 
13
 
14
     fixtures = [BaseFixture, ContentFixtures]
14
     fixtures = [BaseFixture, ContentFixtures]
15
 
15
 
16
-    def test_api__get_html_document__err_400__wrong_content_type(self) -> None:
17
-        """
18
-        Get one html document of a content
19
-        """
20
-        self.testapp.authorization = (
21
-            'Basic',
22
-            (
23
-                'admin@admin.admin',
24
-                'admin@admin.admin'
25
-            )
26
-        )
27
-        res = self.testapp.get(
28
-            '/api/v2/workspaces/2/html-documents/7',
29
-            status=400
30
-        )   # nopep8
31
-
32
     def test_api__get_html_document__ok_200__legacy_slug(self) -> None:
16
     def test_api__get_html_document__ok_200__legacy_slug(self) -> None:
33
         """
17
         """
34
         Get one html document of a content
18
         Get one html document of a content
44
         res = self.testapp.get(
28
         res = self.testapp.get(
45
             '/api/v2/workspaces/2/html-documents/6',
29
             '/api/v2/workspaces/2/html-documents/6',
46
             status=200
30
             status=200
47
-        )   # nopep8
31
+        )
48
         content = res.json_body
32
         content = res.json_body
49
         assert content['content_type'] == 'html-documents'
33
         assert content['content_type'] == 'html-documents'
50
         assert content['content_id'] == 6
34
         assert content['content_id'] == 6
85
         res = self.testapp.get(
69
         res = self.testapp.get(
86
             '/api/v2/workspaces/2/html-documents/6',
70
             '/api/v2/workspaces/2/html-documents/6',
87
             status=200
71
             status=200
88
-        )   # nopep8
72
+        )
89
         content = res.json_body
73
         content = res.json_body
90
         assert content['content_type'] == 'html-documents'
74
         assert content['content_type'] == 'html-documents'
91
         assert content['content_id'] == 6
75
         assert content['content_id'] == 6
112
         assert content['last_modifier']['avatar_url'] is None
96
         assert content['last_modifier']['avatar_url'] is None
113
         assert content['raw_content'] == '<p>To cook a great Tiramisu, you need many ingredients.</p>'  # nopep8
97
         assert content['raw_content'] == '<p>To cook a great Tiramisu, you need many ingredients.</p>'  # nopep8
114
 
98
 
99
+    def test_api__get_html_document__err_400__wrong_content_type(self) -> None:
100
+        """
101
+        Get one html document of a content content 7 is not html_document
102
+        """
103
+        self.testapp.authorization = (
104
+            'Basic',
105
+            (
106
+                'admin@admin.admin',
107
+                'admin@admin.admin'
108
+            )
109
+        )
110
+        res = self.testapp.get(
111
+            '/api/v2/workspaces/2/html-documents/7',
112
+            status=400
113
+        )
114
+
115
+    def test_api__get_html_document__err_400__content_does_not_exist(self) -> None:  # nopep8
116
+        """
117
+        Get one html document of a content (content 170 does not exist in db
118
+        """
119
+        self.testapp.authorization = (
120
+            'Basic',
121
+            (
122
+                'admin@admin.admin',
123
+                'admin@admin.admin'
124
+            )
125
+        )
126
+        res = self.testapp.get(
127
+            '/api/v2/workspaces/2/html-documents/170',
128
+            status=400
129
+        )
130
+
131
+    def test_api__get_html_document__err_400__content_not_in_workspace(self) -> None:  # nopep8
132
+        """
133
+        Get one html document of a content (content 6 is in workspace 2)
134
+        """
135
+        self.testapp.authorization = (
136
+            'Basic',
137
+            (
138
+                'admin@admin.admin',
139
+                'admin@admin.admin'
140
+            )
141
+        )
142
+        res = self.testapp.get(
143
+            '/api/v2/workspaces/1/html-documents/6',
144
+            status=400
145
+        )
146
+
147
+    def test_api__get_html_document__err_400__workspace_does_not_exist(self) -> None:  # nopep8
148
+        """
149
+        Get one html document of a content (Workspace 40 does not exist)
150
+        """
151
+        self.testapp.authorization = (
152
+            'Basic',
153
+            (
154
+                'admin@admin.admin',
155
+                'admin@admin.admin'
156
+            )
157
+        )
158
+        res = self.testapp.get(
159
+            '/api/v2/workspaces/40/html-documents/6',
160
+            status=400
161
+        )
162
+
163
+    def test_api__get_html_document__err_400__workspace_id_is_not_int(self) -> None:  # nopep8
164
+        """
165
+        Get one html document of a content, workspace id is not int
166
+        """
167
+        self.testapp.authorization = (
168
+            'Basic',
169
+            (
170
+                'admin@admin.admin',
171
+                'admin@admin.admin'
172
+            )
173
+        )
174
+        res = self.testapp.get(
175
+            '/api/v2/workspaces/coucou/html-documents/6',
176
+            status=400
177
+        )
178
+
179
+    def test_api__get_html_document__err_400__content_id_is_not_int(self) -> None:  # nopep8
180
+        """
181
+        Get one html document of a content, content_id is not int
182
+        """
183
+        self.testapp.authorization = (
184
+            'Basic',
185
+            (
186
+                'admin@admin.admin',
187
+                'admin@admin.admin'
188
+            )
189
+        )
190
+        res = self.testapp.get(
191
+            '/api/v2/workspaces/2/html-documents/coucou',
192
+            status=400
193
+        )
194
+
195
+    def test_api__update_html_document__err_400__empty_label(self) -> None:  # nopep8
196
+        """
197
+        Update(put) one html document of a content
198
+        """
199
+        self.testapp.authorization = (
200
+            'Basic',
201
+            (
202
+                'admin@admin.admin',
203
+                'admin@admin.admin'
204
+            )
205
+        )
206
+        params = {
207
+            'label': '',
208
+            'raw_content': '<p> Le nouveau contenu </p>',
209
+        }
210
+        res = self.testapp.put_json(
211
+            '/api/v2/workspaces/2/html-documents/6',
212
+            params=params,
213
+            status=400
214
+        )
215
+
115
     def test_api__update_html_document__ok_200__nominal_case(self) -> None:
216
     def test_api__update_html_document__ok_200__nominal_case(self) -> None:
116
         """
217
         """
117
         Update(put) one html document of a content
218
         Update(put) one html document of a content
158
         res = self.testapp.get(
259
         res = self.testapp.get(
159
             '/api/v2/workspaces/2/html-documents/6',
260
             '/api/v2/workspaces/2/html-documents/6',
160
             status=200
261
             status=200
161
-        )   # nopep8
262
+        )
162
         content = res.json_body
263
         content = res.json_body
163
         assert content['content_type'] == 'html-documents'
264
         assert content['content_type'] == 'html-documents'
164
         assert content['content_id'] == 6
265
         assert content['content_id'] == 6
284
         res = self.testapp.get(
385
         res = self.testapp.get(
285
             '/api/v2/workspaces/2/html-documents/6',
386
             '/api/v2/workspaces/2/html-documents/6',
286
             status=200
387
             status=200
287
-        )   # nopep8
388
+        )
288
         content = res.json_body
389
         content = res.json_body
289
         assert content['content_type'] == 'html-documents'
390
         assert content['content_type'] == 'html-documents'
290
         assert content['content_id'] == 6
391
         assert content['content_id'] == 6
301
         res = self.testapp.get(
402
         res = self.testapp.get(
302
             '/api/v2/workspaces/2/html-documents/6',
403
             '/api/v2/workspaces/2/html-documents/6',
303
             status=200
404
             status=200
304
-        )   # nopep8
405
+        )
305
         content = res.json_body
406
         content = res.json_body
306
         assert content['content_type'] == 'html-documents'
407
         assert content['content_type'] == 'html-documents'
307
         assert content['content_id'] == 6
408
         assert content['content_id'] == 6
308
         assert content['status'] == 'closed-deprecated'
409
         assert content['status'] == 'closed-deprecated'
309
 
410
 
411
+    def test_api__set_html_document_status__err_400__wrong_status(self) -> None:
412
+        """
413
+        Get one html document of a content
414
+        """
415
+        self.testapp.authorization = (
416
+            'Basic',
417
+            (
418
+                'admin@admin.admin',
419
+                'admin@admin.admin'
420
+            )
421
+        )
422
+        params = {
423
+            'status': 'unexistant-status',
424
+        }
425
+        res = self.testapp.put_json(
426
+            '/api/v2/workspaces/2/html-documents/6/status',
427
+            params=params,
428
+            status=400
429
+        )
430
+
310
 
431
 
311
 class TestThreads(FunctionalTest):
432
 class TestThreads(FunctionalTest):
312
     """
433
     """
330
         res = self.testapp.get(
451
         res = self.testapp.get(
331
             '/api/v2/workspaces/2/threads/6',
452
             '/api/v2/workspaces/2/threads/6',
332
             status=400
453
             status=400
333
-        )   # nopep8
454
+        )
334
 
455
 
335
     def test_api__get_thread__ok_200__nominal_case(self) -> None:
456
     def test_api__get_thread__ok_200__nominal_case(self) -> None:
336
         """
457
         """
373
         assert content['last_modifier']['avatar_url'] is None
494
         assert content['last_modifier']['avatar_url'] is None
374
         assert content['raw_content'] == 'What is the best cake?'  # nopep8
495
         assert content['raw_content'] == 'What is the best cake?'  # nopep8
375
 
496
 
497
+    def test_api__get_thread__err_400__content_does_not_exist(self) -> None:
498
+        """
499
+        Get one thread (content 170 does not exist)
500
+        """
501
+        self.testapp.authorization = (
502
+            'Basic',
503
+            (
504
+                'admin@admin.admin',
505
+                'admin@admin.admin'
506
+            )
507
+        )
508
+        res = self.testapp.get(
509
+            '/api/v2/workspaces/2/threads/170',
510
+            status=400
511
+        )
512
+
513
+    def test_api__get_thread__err_400__content_not_in_workspace(self) -> None:
514
+        """
515
+        Get one thread(content 7 is in workspace 2)
516
+        """
517
+        self.testapp.authorization = (
518
+            'Basic',
519
+            (
520
+                'admin@admin.admin',
521
+                'admin@admin.admin'
522
+            )
523
+        )
524
+        res = self.testapp.get(
525
+            '/api/v2/workspaces/1/threads/7',
526
+            status=400
527
+        )
528
+
529
+    def test_api__get_thread__err_400__workspace_does_not_exist(self) -> None:  # nopep8
530
+        """
531
+        Get one thread (Workspace 40 does not exist)
532
+        """
533
+        self.testapp.authorization = (
534
+            'Basic',
535
+            (
536
+                'admin@admin.admin',
537
+                'admin@admin.admin'
538
+            )
539
+        )
540
+        res = self.testapp.get(
541
+            '/api/v2/workspaces/40/threads/7',
542
+            status=400
543
+        )
544
+
545
+    def test_api__get_thread__err_400__workspace_id_is_not_int(self) -> None:  # nopep8
546
+        """
547
+        Get one thread, workspace id is not int
548
+        """
549
+        self.testapp.authorization = (
550
+            'Basic',
551
+            (
552
+                'admin@admin.admin',
553
+                'admin@admin.admin'
554
+            )
555
+        )
556
+        res = self.testapp.get(
557
+            '/api/v2/workspaces/coucou/threads/7',
558
+            status=400
559
+        )
560
+
561
+    def test_api__get_thread__err_400_content_id_is_not_int(self) -> None:  # nopep8
562
+        """
563
+        Get one thread, content id is not int
564
+        """
565
+        self.testapp.authorization = (
566
+            'Basic',
567
+            (
568
+                'admin@admin.admin',
569
+                'admin@admin.admin'
570
+            )
571
+        )
572
+        res = self.testapp.get(
573
+            '/api/v2/workspaces/2/threads/coucou',
574
+            status=400
575
+        )
576
+
376
     def test_api__update_thread__ok_200__nominal_case(self) -> None:
577
     def test_api__update_thread__ok_200__nominal_case(self) -> None:
377
         """
578
         """
378
-        Update(put) one html document of a content
579
+        Update(put) thread
379
         """
580
         """
380
         self.testapp.authorization = (
581
         self.testapp.authorization = (
381
             'Basic',
582
             'Basic',
443
         assert content['last_modifier'] == content['author']
644
         assert content['last_modifier'] == content['author']
444
         assert content['raw_content'] == '<p> Le nouveau contenu </p>'
645
         assert content['raw_content'] == '<p> Le nouveau contenu </p>'
445
 
646
 
647
+    def test_api__update_thread__err_400__empty_label(self) -> None:
648
+        """
649
+        Update(put) thread
650
+        """
651
+        self.testapp.authorization = (
652
+            'Basic',
653
+            (
654
+                'admin@admin.admin',
655
+                'admin@admin.admin'
656
+            )
657
+        )
658
+        params = {
659
+            'label': '',
660
+            'raw_content': '<p> Le nouveau contenu </p>',
661
+        }
662
+        res = self.testapp.put_json(
663
+            '/api/v2/workspaces/2/threads/7',
664
+            params=params,
665
+            status=400
666
+        )
667
+
446
     def test_api__get_thread_revisions__ok_200__nominal_case(
668
     def test_api__get_thread_revisions__ok_200__nominal_case(
447
             self
669
             self
448
     ) -> None:
670
     ) -> None:
449
         """
671
         """
450
-        Get one html document of a content
672
+        Get threads revisions
451
         """
673
         """
452
         self.testapp.authorization = (
674
         self.testapp.authorization = (
453
             'Basic',
675
             'Basic',
505
 
727
 
506
     def test_api__set_thread_status__ok_200__nominal_case(self) -> None:
728
     def test_api__set_thread_status__ok_200__nominal_case(self) -> None:
507
         """
729
         """
508
-        Get one html document of a content
730
+        Set thread status
509
         """
731
         """
510
         self.testapp.authorization = (
732
         self.testapp.authorization = (
511
             'Basic',
733
             'Basic',
544
         assert content['content_type'] == 'thread'
766
         assert content['content_type'] == 'thread'
545
         assert content['content_id'] == 7
767
         assert content['content_id'] == 7
546
         assert content['status'] == 'closed-deprecated'
768
         assert content['status'] == 'closed-deprecated'
769
+
770
+    def test_api__set_thread_status__ok_400__wrong_status(self) -> None:
771
+        """
772
+        Set thread status
773
+        """
774
+        self.testapp.authorization = (
775
+            'Basic',
776
+            (
777
+                'admin@admin.admin',
778
+                'admin@admin.admin'
779
+            )
780
+        )
781
+        params = {
782
+            'status': 'unexistant-status',
783
+        }
784
+
785
+        res = self.testapp.put_json(
786
+            '/api/v2/workspaces/2/threads/7/status',
787
+            params=params,
788
+            status=400
789
+        )

+ 2 - 2
tracim/tests/functional/test_user.py Целия файл

118
         assert 'message' in res.json.keys()
118
         assert 'message' in res.json.keys()
119
         assert 'details' in res.json.keys()
119
         assert 'details' in res.json.keys()
120
 
120
 
121
-    def test_api__get_user_workspaces__err_404__user_does_not_exist(self):
121
+    def test_api__get_user_workspaces__err_400__user_does_not_exist(self):
122
         """
122
         """
123
         Check obtain all workspaces reachables for one user who does
123
         Check obtain all workspaces reachables for one user who does
124
         not exist
124
         not exist
131
                 'admin@admin.admin'
131
                 'admin@admin.admin'
132
             )
132
             )
133
         )
133
         )
134
-        res = self.testapp.get('/api/v2/users/5/workspaces', status=404)
134
+        res = self.testapp.get('/api/v2/users/5/workspaces', status=400)
135
         assert isinstance(res.json, dict)
135
         assert isinstance(res.json, dict)
136
         assert 'code' in res.json.keys()
136
         assert 'code' in res.json.keys()
137
         assert 'message' in res.json.keys()
137
         assert 'message' in res.json.keys()

+ 54 - 12
tracim/tests/functional/test_workspaces.py Целия файл

83
         assert sidebar_entry['hexcolor'] == "#757575"
83
         assert sidebar_entry['hexcolor'] == "#757575"
84
         assert sidebar_entry['fa_icon'] == "calendar"
84
         assert sidebar_entry['fa_icon'] == "calendar"
85
 
85
 
86
-    def test_api__get_workspace__err_403__unallowed_user(self) -> None:
86
+    def test_api__get_workspace__err_400__unallowed_user(self) -> None:
87
         """
87
         """
88
         Check obtain workspace unreachable for user
88
         Check obtain workspace unreachable for user
89
         """
89
         """
94
                 'foobarbaz'
94
                 'foobarbaz'
95
             )
95
             )
96
         )
96
         )
97
-        res = self.testapp.get('/api/v2/workspaces/1', status=403)
97
+        res = self.testapp.get('/api/v2/workspaces/1', status=400)
98
         assert isinstance(res.json, dict)
98
         assert isinstance(res.json, dict)
99
         assert 'code' in res.json.keys()
99
         assert 'code' in res.json.keys()
100
         assert 'message' in res.json.keys()
100
         assert 'message' in res.json.keys()
117
         assert 'message' in res.json.keys()
117
         assert 'message' in res.json.keys()
118
         assert 'details' in res.json.keys()
118
         assert 'details' in res.json.keys()
119
 
119
 
120
-    def test_api__get_workspace__err_403__workspace_does_not_exist(self) -> None:  # nopep8
120
+    def test_api__get_workspace__err_400__workspace_does_not_exist(self) -> None:  # nopep8
121
         """
121
         """
122
         Check obtain workspace who does not exist with an existing user.
122
         Check obtain workspace who does not exist with an existing user.
123
         """
123
         """
128
                 'admin@admin.admin'
128
                 'admin@admin.admin'
129
             )
129
             )
130
         )
130
         )
131
-        res = self.testapp.get('/api/v2/workspaces/5', status=403)
131
+        res = self.testapp.get('/api/v2/workspaces/5', status=400)
132
         assert isinstance(res.json, dict)
132
         assert isinstance(res.json, dict)
133
         assert 'code' in res.json.keys()
133
         assert 'code' in res.json.keys()
134
         assert 'message' in res.json.keys()
134
         assert 'message' in res.json.keys()
164
         # by correct value when avatar feature will be enabled
164
         # by correct value when avatar feature will be enabled
165
         assert user_role['user']['avatar_url'] is None
165
         assert user_role['user']['avatar_url'] is None
166
 
166
 
167
-    def test_api__get_workspace_members__err_403__unallowed_user(self):
167
+    def test_api__get_workspace_members__err_400__unallowed_user(self):
168
         """
168
         """
169
         Check obtain workspace members list with an unreachable workspace for
169
         Check obtain workspace members list with an unreachable workspace for
170
         user
170
         user
176
                 'foobarbaz'
176
                 'foobarbaz'
177
             )
177
             )
178
         )
178
         )
179
-        res = self.testapp.get('/api/v2/workspaces/3/members', status=403)
179
+        res = self.testapp.get('/api/v2/workspaces/3/members', status=400)
180
         assert isinstance(res.json, dict)
180
         assert isinstance(res.json, dict)
181
         assert 'code' in res.json.keys()
181
         assert 'code' in res.json.keys()
182
         assert 'message' in res.json.keys()
182
         assert 'message' in res.json.keys()
199
         assert 'message' in res.json.keys()
199
         assert 'message' in res.json.keys()
200
         assert 'details' in res.json.keys()
200
         assert 'details' in res.json.keys()
201
 
201
 
202
-    def test_api__get_workspace_members__err_403__workspace_does_not_exist(self):  # nopep8
202
+    def test_api__get_workspace_members__err_400__workspace_does_not_exist(self):  # nopep8
203
         """
203
         """
204
         Check obtain workspace members list with an existing user but
204
         Check obtain workspace members list with an existing user but
205
         an unexisting workspace
205
         an unexisting workspace
211
                 'admin@admin.admin'
211
                 'admin@admin.admin'
212
             )
212
             )
213
         )
213
         )
214
-        res = self.testapp.get('/api/v2/workspaces/5/members', status=403)
214
+        res = self.testapp.get('/api/v2/workspaces/5/members', status=400)
215
         assert isinstance(res.json, dict)
215
         assert isinstance(res.json, dict)
216
         assert 'code' in res.json.keys()
216
         assert 'code' in res.json.keys()
217
         assert 'message' in res.json.keys()
217
         assert 'message' in res.json.keys()
739
 
739
 
740
     # Error case
740
     # Error case
741
 
741
 
742
-    def test_api__get_workspace_content__err_403__unallowed_user(self):
742
+    def test_api__get_workspace_content__err_400__unallowed_user(self):
743
         """
743
         """
744
         Check obtain workspace content list with an unreachable workspace for
744
         Check obtain workspace content list with an unreachable workspace for
745
         user
745
         user
751
                 'foobarbaz'
751
                 'foobarbaz'
752
             )
752
             )
753
         )
753
         )
754
-        res = self.testapp.get('/api/v2/workspaces/3/contents', status=403)
754
+        res = self.testapp.get('/api/v2/workspaces/3/contents', status=400)
755
         assert isinstance(res.json, dict)
755
         assert isinstance(res.json, dict)
756
         assert 'code' in res.json.keys()
756
         assert 'code' in res.json.keys()
757
         assert 'message' in res.json.keys()
757
         assert 'message' in res.json.keys()
774
         assert 'message' in res.json.keys()
774
         assert 'message' in res.json.keys()
775
         assert 'details' in res.json.keys()
775
         assert 'details' in res.json.keys()
776
 
776
 
777
-    def test_api__get_workspace_content__err_403__workspace_does_not_exist(self):  # nopep8
777
+    def test_api__get_workspace_content__err_400__workspace_does_not_exist(self):  # nopep8
778
         """
778
         """
779
         Check obtain workspace contents list with an existing user but
779
         Check obtain workspace contents list with an existing user but
780
         an unexisting workspace
780
         an unexisting workspace
786
                 'admin@admin.admin'
786
                 'admin@admin.admin'
787
             )
787
             )
788
         )
788
         )
789
-        res = self.testapp.get('/api/v2/workspaces/5/contents', status=403)
789
+        res = self.testapp.get('/api/v2/workspaces/5/contents', status=400)
790
         assert isinstance(res.json, dict)
790
         assert isinstance(res.json, dict)
791
         assert 'code' in res.json.keys()
791
         assert 'code' in res.json.keys()
792
         assert 'message' in res.json.keys()
792
         assert 'message' in res.json.keys()
834
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
834
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
835
         assert res.json_body in active_contents
835
         assert res.json_body in active_contents
836
 
836
 
837
+    def test_api__post_content_create_generic_content__err_400__empty_label(self) -> None:  # nopep8
838
+        """
839
+        Create generic content
840
+        """
841
+        self.testapp.authorization = (
842
+            'Basic',
843
+            (
844
+                'admin@admin.admin',
845
+                'admin@admin.admin'
846
+            )
847
+        )
848
+        params = {
849
+            'label': '',
850
+            'content_type': 'markdownpage',
851
+        }
852
+        res = self.testapp.post_json(
853
+            '/api/v2/workspaces/1/contents',
854
+            params=params,
855
+            status=400
856
+        )
857
+
858
+    def test_api__post_content_create_generic_content__err_400__wrong_content_type(self) -> None:  # nopep8
859
+        """
860
+        Create generic content
861
+        """
862
+        self.testapp.authorization = (
863
+            'Basic',
864
+            (
865
+                'admin@admin.admin',
866
+                'admin@admin.admin'
867
+            )
868
+        )
869
+        params = {
870
+            'label': 'GenericCreatedContent',
871
+            'content_type': 'unexistent-content-type',
872
+        }
873
+        res = self.testapp.post_json(
874
+            '/api/v2/workspaces/1/contents',
875
+            params=params,
876
+            status=400,
877
+        )
878
+
837
     def test_api_put_move_content__ok_200__nominal_case(self):
879
     def test_api_put_move_content__ok_200__nominal_case(self):
838
         """
880
         """
839
         Move content
881
         Move content

+ 102 - 30
tracim/tests/library/test_content_api.py Целия файл

128
             session=self.session,
128
             session=self.session,
129
             config=self.app_config,
129
             config=self.app_config,
130
         )
130
         )
131
-        item = api.create(ContentType.Folder, workspace, None,
132
-                          'not_deleted', True)
133
-        item2 = api.create(ContentType.Folder, workspace, None,
134
-                           'to_delete', True)
131
+        item = api.create(
132
+            content_type=ContentType.Folder,
133
+            workspace=workspace,
134
+            parent=None,
135
+            label='not_deleted',
136
+            do_save=True
137
+        )
138
+        item2 = api.create(
139
+            content_type=ContentType.Folder,
140
+            workspace=workspace,
141
+            parent=None,
142
+            label='to_delete',
143
+            do_save=True
144
+        )
135
         uid = user.user_id
145
         uid = user.user_id
136
         wid = workspace.workspace_id
146
         wid = workspace.workspace_id
137
         transaction.commit()
147
         transaction.commit()
229
             session=self.session,
239
             session=self.session,
230
             config=self.app_config,
240
             config=self.app_config,
231
         )
241
         )
232
-        item = api.create(ContentType.Folder, workspace, None,
233
-                          'not_archived', True)
234
-        item2 = api.create(ContentType.Folder, workspace, None,
235
-                           'to_archive', True)
242
+        item = api.create(
243
+            content_type=ContentType.Folder,
244
+            workspace=workspace,
245
+            parent=None,
246
+            label='not_archived',
247
+            do_save=True
248
+        )
249
+        item2 = api.create(
250
+            content_type=ContentType.Folder,
251
+            workspace=workspace,
252
+            parent=None,
253
+            label='to_archive',
254
+            do_save=True
255
+        )
236
         uid = user.user_id
256
         uid = user.user_id
237
         wid = workspace.workspace_id
257
         wid = workspace.workspace_id
238
         transaction.commit()
258
         transaction.commit()
337
             session=self.session,
357
             session=self.session,
338
             config=self.app_config,
358
             config=self.app_config,
339
         )
359
         )
340
-        item = api.create(ContentType.Folder, workspace, None,
341
-                          'thefolder', True)
342
-        item2 = api.create(ContentType.File, workspace, None, 'thefile', True)
360
+        item = api.create(
361
+            content_type=ContentType.Folder,
362
+            workspace=workspace,
363
+            parent=None,
364
+            label='thefolder',
365
+            do_save=True
366
+        )
367
+        item2 = api.create(
368
+            content_type=ContentType.File,
369
+            workspace=workspace,
370
+            parent=None,
371
+            label='thefile',
372
+            do_save=True
373
+        )
343
         uid = user.user_id
374
         uid = user.user_id
344
         wid = workspace.workspace_id
375
         wid = workspace.workspace_id
345
         transaction.commit()
376
         transaction.commit()
475
             session=self.session,
506
             session=self.session,
476
             config=self.app_config,
507
             config=self.app_config,
477
         )
508
         )
478
-        c = api.create(ContentType.Folder, workspace, None, 'parent', True)
509
+        c = api.create(ContentType.Folder, workspace, None, 'parent', '', True)
479
         with new_revision(
510
         with new_revision(
480
             session=self.session,
511
             session=self.session,
481
             tm=transaction.manager,
512
             tm=transaction.manager,
515
             session=self.session,
546
             session=self.session,
516
             config=self.app_config,
547
             config=self.app_config,
517
         )
548
         )
518
-        c = api.create(ContentType.Folder, workspace, None, 'parent', True)
549
+        c = api.create(ContentType.Folder, workspace, None, 'parent', '', True)
519
         with new_revision(
550
         with new_revision(
520
             session=self.session,
551
             session=self.session,
521
             tm=transaction.manager,
552
             tm=transaction.manager,
625
             workspace,
656
             workspace,
626
             None,
657
             None,
627
             'folder a',
658
             'folder a',
659
+            '',
628
             True
660
             True
629
         )
661
         )
630
         with self.session.no_autoflush:
662
         with self.session.no_autoflush:
661
             workspace2,
693
             workspace2,
662
             None,
694
             None,
663
             'folder b',
695
             'folder b',
696
+            '',
664
             True
697
             True
665
         )
698
         )
666
 
699
 
744
             workspace,
777
             workspace,
745
             None,
778
             None,
746
             'folder a',
779
             'folder a',
780
+            '',
747
             True
781
             True
748
         )
782
         )
749
         with self.session.no_autoflush:
783
         with self.session.no_autoflush:
780
             workspace2,
814
             workspace2,
781
             None,
815
             None,
782
             'folder b',
816
             'folder b',
817
+            '',
783
             True
818
             True
784
         )
819
         )
785
         api2.copy(
820
         api2.copy(
860
             workspace,
895
             workspace,
861
             None,
896
             None,
862
             'folder a',
897
             'folder a',
898
+            '',
863
             True
899
             True
864
         )
900
         )
865
         with self.session.no_autoflush:
901
         with self.session.no_autoflush:
1248
             config=self.app_config,
1284
             config=self.app_config,
1249
         )
1285
         )
1250
 
1286
 
1251
-        p = api.create(ContentType.Page, workspace, None,
1252
-                       'this_is_a_page', True)
1287
+        p = api.create(
1288
+            content_type=ContentType.Page,
1289
+            workspace=workspace,
1290
+            parent=None,
1291
+            label='this_is_a_page',
1292
+            do_save=True
1293
+        )
1253
 
1294
 
1254
         u1id = user1.user_id
1295
         u1id = user1.user_id
1255
         u2id = user2.user_id
1296
         u2id = user2.user_id
1453
             session=self.session,
1494
             session=self.session,
1454
             config=self.app_config,
1495
             config=self.app_config,
1455
         )
1496
         )
1456
-        p = api.create(ContentType.File, workspace, None,
1457
-                       'this_is_a_page', True)
1497
+        p = api.create(
1498
+            content_type=ContentType.File,
1499
+            workspace=workspace,
1500
+            parent=None,
1501
+            label='this_is_a_page',
1502
+            do_save=True
1503
+        )
1458
 
1504
 
1459
         u1id = user1.user_id
1505
         u1id = user1.user_id
1460
         u2id = user2.user_id
1506
         u2id = user2.user_id
1666
             show_archived=True,
1712
             show_archived=True,
1667
             config=self.app_config,
1713
             config=self.app_config,
1668
         )
1714
         )
1669
-        p = api.create(ContentType.File, workspace, None,
1670
-                       'this_is_a_page', True)
1715
+        p = api.create(
1716
+            content_type=ContentType.File,
1717
+            workspace=workspace,
1718
+            parent=None,
1719
+            label='this_is_a_page',
1720
+            do_save=True
1721
+        )
1671
 
1722
 
1672
         u1id = user1.user_id
1723
         u1id = user1.user_id
1673
         u2id = user2.user_id
1724
         u2id = user2.user_id
1822
             config=self.app_config,
1873
             config=self.app_config,
1823
             show_deleted=True,
1874
             show_deleted=True,
1824
         )
1875
         )
1825
-        p = api.create(ContentType.File, workspace, None,
1826
-                       'this_is_a_page', True)
1876
+        p = api.create(
1877
+            content_type=ContentType.File,
1878
+            workspace=workspace,
1879
+            parent=None,
1880
+            label='this_is_a_page',
1881
+            do_save=True
1882
+        )
1827
 
1883
 
1828
         u1id = user1.user_id
1884
         u1id = user1.user_id
1829
         u2id = user2.user_id
1885
         u2id = user2.user_id
1957
             config=self.app_config,
2013
             config=self.app_config,
1958
         )
2014
         )
1959
         a = api.create(ContentType.Folder, workspace, None,
2015
         a = api.create(ContentType.Folder, workspace, None,
1960
-                       'this is randomized folder', True)
2016
+                       'this is randomized folder', '', True)
1961
         p = api.create(ContentType.Page, workspace, a,
2017
         p = api.create(ContentType.Page, workspace, a,
1962
-                       'this is randomized label content', True)
2018
+                       'this is randomized label content', '', True)
1963
 
2019
 
1964
         with new_revision(
2020
         with new_revision(
1965
             session=self.session,
2021
             session=self.session,
2013
             config=self.app_config,
2069
             config=self.app_config,
2014
         )
2070
         )
2015
         a = api.create(ContentType.Folder, workspace, None,
2071
         a = api.create(ContentType.Folder, workspace, None,
2016
-                       'this is randomized folder', True)
2072
+                       'this is randomized folder', '', True)
2017
         p = api.create(ContentType.Page, workspace, a,
2073
         p = api.create(ContentType.Page, workspace, a,
2018
-                       'this is dummy label content', True)
2074
+                       'this is dummy label content', '', True)
2019
 
2075
 
2020
         with new_revision(
2076
         with new_revision(
2021
             tm=transaction.manager,
2077
             tm=transaction.manager,
2065
             session=self.session,
2121
             session=self.session,
2066
             config=self.app_config,
2122
             config=self.app_config,
2067
         )
2123
         )
2068
-        a = api.create(ContentType.Folder, workspace, None,
2069
-                       'this is randomized folder', True)
2070
-        p1 = api.create(ContentType.Page, workspace, a,
2071
-                        'this is dummy label content', True)
2072
-        p2 = api.create(ContentType.Page, workspace, a, 'Hey ! Jon !', True)
2124
+        a = api.create(
2125
+            content_type=ContentType.Folder,
2126
+            workspace=workspace,
2127
+            parent=None,
2128
+            label='this is randomized folder',
2129
+            do_save=True
2130
+        )
2131
+        p1 = api.create(
2132
+            content_type=ContentType.Page,
2133
+            workspace=workspace,
2134
+            parent=a,
2135
+            label='this is dummy label content',
2136
+            do_save=True
2137
+        )
2138
+        p2 = api.create(
2139
+            content_type=ContentType.Page,
2140
+            workspace=workspace,
2141
+            parent=a,
2142
+            label='Hey ! Jon !',
2143
+            do_save=True
2144
+        )
2073
 
2145
 
2074
         with new_revision(
2146
         with new_revision(
2075
             session=self.session,
2147
             session=self.session,

+ 1 - 0
tracim/tests/library/test_webdav.py Целия файл

95
                        self.session,
95
                        self.session,
96
                        self.app_config
96
                        self.app_config
97
                        ).get_one_by_email(email)
97
                        ).get_one_by_email(email)
98
+
98
     def _put_new_text_file(
99
     def _put_new_text_file(
99
             self,
100
             self,
100
             provider,
101
             provider,

+ 4 - 18
tracim/views/contents_api/comment_controller.py Целия файл

19
 from tracim.views.core_api.schemas import SetCommentSchema
19
 from tracim.views.core_api.schemas import SetCommentSchema
20
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
20
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21
 from tracim.views.core_api.schemas import NoContentSchema
21
 from tracim.views.core_api.schemas import NoContentSchema
22
-from tracim.exceptions import WorkspaceNotFound
23
-from tracim.exceptions import InsufficientUserRoleInWorkspace
24
-from tracim.exceptions import NotAuthenticated
25
-from tracim.exceptions import AuthenticationFailed
22
+from tracim.exceptions import EmptyCommentContentNotAllowed
26
 from tracim.models.contents import ContentTypeLegacy as ContentType
23
 from tracim.models.contents import ContentTypeLegacy as ContentType
27
 from tracim.models.revision_protection import new_revision
24
 from tracim.models.revision_protection import new_revision
28
 from tracim.models.data import UserRoleInWorkspace
25
 from tracim.models.data import UserRoleInWorkspace
33
 class CommentController(Controller):
30
 class CommentController(Controller):
34
 
31
 
35
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
32
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
36
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
37
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
38
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
39
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
40
     @require_workspace_role(UserRoleInWorkspace.READER)
33
     @require_workspace_role(UserRoleInWorkspace.READER)
41
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
34
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
42
-    @hapic.output_body(CommentSchema(many=True),)
35
+    @hapic.output_body(CommentSchema(many=True))
43
     def content_comments(self, context, request: TracimRequest, hapic_data=None):
36
     def content_comments(self, context, request: TracimRequest, hapic_data=None):
44
         """
37
         """
45
         Get all comments related to a content in asc order (first is the oldest)
38
         Get all comments related to a content in asc order (first is the oldest)
63
         ]
56
         ]
64
 
57
 
65
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
58
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
66
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
67
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
68
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
69
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
59
+    @hapic.handle_exception(EmptyCommentContentNotAllowed, HTTPStatus.BAD_REQUEST)  # nopep8
70
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
60
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
71
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
61
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
72
     @hapic.input_body(SetCommentSchema())
62
     @hapic.input_body(SetCommentSchema())
73
-    @hapic.output_body(CommentSchema(),)
63
+    @hapic.output_body(CommentSchema())
74
     def add_comment(self, context, request: TracimRequest, hapic_data=None):
64
     def add_comment(self, context, request: TracimRequest, hapic_data=None):
75
         """
65
         """
76
         Add new comment
66
         Add new comment
95
         return api.get_content_in_context(comment)
85
         return api.get_content_in_context(comment)
96
 
86
 
97
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
87
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
98
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
99
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
100
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
101
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
102
     @require_comment_ownership_or_role(
88
     @require_comment_ownership_or_role(
103
         minimal_required_role_for_anyone=UserRoleInWorkspace.WORKSPACE_MANAGER,
89
         minimal_required_role_for_anyone=UserRoleInWorkspace.WORKSPACE_MANAGER,
104
         minimal_required_role_for_owner=UserRoleInWorkspace.CONTRIBUTOR,
90
         minimal_required_role_for_owner=UserRoleInWorkspace.CONTRIBUTOR,

+ 9 - 29
tracim/views/contents_api/html_document_controller.py Целия файл

15
 from tracim.extensions import hapic
15
 from tracim.extensions import hapic
16
 from tracim.lib.core.content import ContentApi
16
 from tracim.lib.core.content import ContentApi
17
 from tracim.views.controllers import Controller
17
 from tracim.views.controllers import Controller
18
-from tracim.views.core_api.schemas import HtmlDocumentContentSchema
19
-from tracim.views.core_api.schemas import HtmlDocumentRevisionSchema
18
+from tracim.views.core_api.schemas import TextBasedContentSchema
19
+from tracim.views.core_api.schemas import TextBasedRevisionSchema
20
+from tracim.views.core_api.schemas import TextBasedContentModifySchema
20
 from tracim.views.core_api.schemas import SetContentStatusSchema
21
 from tracim.views.core_api.schemas import SetContentStatusSchema
21
-from tracim.views.core_api.schemas import HtmlDocumentModifySchema
22
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
22
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
23
 from tracim.views.core_api.schemas import NoContentSchema
23
 from tracim.views.core_api.schemas import NoContentSchema
24
 from tracim.lib.utils.authorization import require_content_types
24
 from tracim.lib.utils.authorization import require_content_types
25
 from tracim.lib.utils.authorization import require_workspace_role
25
 from tracim.lib.utils.authorization import require_workspace_role
26
-from tracim.exceptions import WorkspaceNotFound
27
-from tracim.exceptions import ContentTypeNotAllowed
28
-from tracim.exceptions import InsufficientUserRoleInWorkspace
29
-from tracim.exceptions import NotAuthenticated
30
-from tracim.exceptions import AuthenticationFailed
26
+from tracim.exceptions import EmptyLabelNotAllowed
31
 from tracim.models.context_models import ContentInContext
27
 from tracim.models.context_models import ContentInContext
32
 from tracim.models.context_models import RevisionInContext
28
 from tracim.models.context_models import RevisionInContext
33
 from tracim.models.contents import ContentTypeLegacy as ContentType
29
 from tracim.models.contents import ContentTypeLegacy as ContentType
40
 class HTMLDocumentController(Controller):
36
 class HTMLDocumentController(Controller):
41
 
37
 
42
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
38
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
43
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
44
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
45
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
46
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
47
-    @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
48
     @require_workspace_role(UserRoleInWorkspace.READER)
39
     @require_workspace_role(UserRoleInWorkspace.READER)
49
     @require_content_types([html_documents_type])
40
     @require_content_types([html_documents_type])
50
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
51
-    @hapic.output_body(HtmlDocumentContentSchema())
42
+    @hapic.output_body(TextBasedContentSchema())
52
     def get_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
43
     def get_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
53
         """
44
         """
54
         Get html document content
45
         Get html document content
66
         return api.get_content_in_context(content)
57
         return api.get_content_in_context(content)
67
 
58
 
68
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
59
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
69
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
70
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
71
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
72
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
60
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
73
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
61
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
74
     @require_content_types([html_documents_type])
62
     @require_content_types([html_documents_type])
75
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
63
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
76
-    @hapic.input_body(HtmlDocumentModifySchema())
77
-    @hapic.output_body(HtmlDocumentContentSchema())
64
+    @hapic.input_body(TextBasedContentModifySchema())
65
+    @hapic.output_body(TextBasedContentSchema())
78
     def update_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
66
     def update_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
79
         """
67
         """
80
         update_html_document
68
         update_html_document
104
         return api.get_content_in_context(content)
92
         return api.get_content_in_context(content)
105
 
93
 
106
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
94
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
107
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
108
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
109
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
110
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
111
     @require_workspace_role(UserRoleInWorkspace.READER)
95
     @require_workspace_role(UserRoleInWorkspace.READER)
112
     @require_content_types([html_documents_type])
96
     @require_content_types([html_documents_type])
113
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
97
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
114
-    @hapic.output_body(HtmlDocumentRevisionSchema(many=True))
98
+    @hapic.output_body(TextBasedRevisionSchema(many=True))
115
     def get_html_document_revisions(
99
     def get_html_document_revisions(
116
             self,
100
             self,
117
             context,
101
             context,
138
         ]
122
         ]
139
 
123
 
140
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
124
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
141
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
142
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
143
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
144
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
145
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
125
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
146
     @require_content_types([html_documents_type])
126
     @require_content_types([html_documents_type])
147
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
127
     @hapic.input_path(WorkspaceAndContentIdPathSchema())

+ 9 - 28
tracim/views/contents_api/threads_controller.py Целия файл

14
 from tracim.extensions import hapic
14
 from tracim.extensions import hapic
15
 from tracim.lib.core.content import ContentApi
15
 from tracim.lib.core.content import ContentApi
16
 from tracim.views.controllers import Controller
16
 from tracim.views.controllers import Controller
17
-from tracim.views.core_api.schemas import ThreadContentSchema
18
-from tracim.views.core_api.schemas import ThreadRevisionSchema
17
+from tracim.views.core_api.schemas import TextBasedContentSchema
18
+from tracim.views.core_api.schemas import TextBasedRevisionSchema
19
 from tracim.views.core_api.schemas import SetContentStatusSchema
19
 from tracim.views.core_api.schemas import SetContentStatusSchema
20
-from tracim.views.core_api.schemas import ThreadModifySchema
20
+from tracim.views.core_api.schemas import TextBasedContentModifySchema
21
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
22
 from tracim.views.core_api.schemas import NoContentSchema
22
 from tracim.views.core_api.schemas import NoContentSchema
23
 from tracim.lib.utils.authorization import require_content_types
23
 from tracim.lib.utils.authorization import require_content_types
24
 from tracim.lib.utils.authorization import require_workspace_role
24
 from tracim.lib.utils.authorization import require_workspace_role
25
-from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
26
-from tracim.exceptions import InsufficientUserRoleInWorkspace
27
-from tracim.exceptions import NotAuthenticated
28
-from tracim.exceptions import AuthenticationFailed
25
+from tracim.exceptions import EmptyLabelNotAllowed
29
 from tracim.models.context_models import ContentInContext
26
 from tracim.models.context_models import ContentInContext
30
 from tracim.models.context_models import RevisionInContext
27
 from tracim.models.context_models import RevisionInContext
31
 from tracim.models.contents import ContentTypeLegacy as ContentType
28
 from tracim.models.contents import ContentTypeLegacy as ContentType
38
 class ThreadController(Controller):
35
 class ThreadController(Controller):
39
 
36
 
40
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
37
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
41
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
42
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
43
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
44
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
45
-    @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
46
     @require_workspace_role(UserRoleInWorkspace.READER)
38
     @require_workspace_role(UserRoleInWorkspace.READER)
47
     @require_content_types([thread_type])
39
     @require_content_types([thread_type])
48
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
40
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
49
-    @hapic.output_body(ThreadContentSchema())
41
+    @hapic.output_body(TextBasedContentSchema())
50
     def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
42
     def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
51
         """
43
         """
52
         Get thread content
44
         Get thread content
64
         return api.get_content_in_context(content)
56
         return api.get_content_in_context(content)
65
 
57
 
66
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
58
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
67
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
68
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
69
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
70
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
59
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
71
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
60
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
72
     @require_content_types([thread_type])
61
     @require_content_types([thread_type])
73
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
62
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
74
-    @hapic.input_body(ThreadModifySchema())
75
-    @hapic.output_body(ThreadContentSchema())
63
+    @hapic.input_body(TextBasedContentModifySchema())
64
+    @hapic.output_body(TextBasedContentSchema())
76
     def update_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
65
     def update_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
77
         """
66
         """
78
         update thread
67
         update thread
102
         return api.get_content_in_context(content)
91
         return api.get_content_in_context(content)
103
 
92
 
104
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
93
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
105
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
106
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
107
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
108
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
109
     @require_workspace_role(UserRoleInWorkspace.READER)
94
     @require_workspace_role(UserRoleInWorkspace.READER)
110
     @require_content_types([thread_type])
95
     @require_content_types([thread_type])
111
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
96
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
112
-    @hapic.output_body(ThreadRevisionSchema(many=True))
97
+    @hapic.output_body(TextBasedRevisionSchema(many=True))
113
     def get_thread_revisions(
98
     def get_thread_revisions(
114
             self,
99
             self,
115
             context,
100
             context,
136
         ]
121
         ]
137
 
122
 
138
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
123
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
139
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
140
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
141
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
142
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
143
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
124
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
144
     @require_content_types([thread_type])
125
     @require_content_types([thread_type])
145
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
126
     @hapic.input_path(WorkspaceAndContentIdPathSchema())

+ 106 - 80
tracim/views/core_api/schemas.py Целия файл

2
 import marshmallow
2
 import marshmallow
3
 from marshmallow import post_load
3
 from marshmallow import post_load
4
 from marshmallow.validate import OneOf
4
 from marshmallow.validate import OneOf
5
+from marshmallow.validate import Range
5
 
6
 
6
 from tracim.lib.utils.utils import DATETIME_FORMAT
7
 from tracim.lib.utils.utils import DATETIME_FORMAT
7
 from tracim.models.auth import Profile
8
 from tracim.models.auth import Profile
8
-from tracim.models.contents import CONTENT_DEFAULT_TYPE
9
-from tracim.models.contents import CONTENT_DEFAULT_STATUS
10
 from tracim.models.contents import GlobalStatus
9
 from tracim.models.contents import GlobalStatus
11
 from tracim.models.contents import open_status
10
 from tracim.models.contents import open_status
11
+from tracim.models.contents import ContentTypeLegacy as ContentType
12
+from tracim.models.contents import ContentStatusLegacy as ContentStatus
12
 from tracim.models.context_models import ContentCreation
13
 from tracim.models.context_models import ContentCreation
13
-from tracim.models.context_models import SetContentStatus
14
 from tracim.models.context_models import CommentCreation
14
 from tracim.models.context_models import CommentCreation
15
+from tracim.models.context_models import TextBasedContentUpdate
16
+from tracim.models.context_models import SetContentStatus
15
 from tracim.models.context_models import CommentPath
17
 from tracim.models.context_models import CommentPath
16
 from tracim.models.context_models import MoveParams
18
 from tracim.models.context_models import MoveParams
17
 from tracim.models.context_models import WorkspaceAndContentPath
19
 from tracim.models.context_models import WorkspaceAndContentPath
18
 from tracim.models.context_models import ContentFilter
20
 from tracim.models.context_models import ContentFilter
19
 from tracim.models.context_models import LoginCredentials
21
 from tracim.models.context_models import LoginCredentials
20
-from tracim.models.context_models import HTMLDocumentUpdate
21
-from tracim.models.context_models import ThreadUpdate
22
 from tracim.models.data import UserRoleInWorkspace
22
 from tracim.models.data import UserRoleInWorkspace
23
 
23
 
24
 
24
 
80
 
80
 
81
 
81
 
82
 class UserIdPathSchema(marshmallow.Schema):
82
 class UserIdPathSchema(marshmallow.Schema):
83
-    user_id = marshmallow.fields.Int(example=3, required=True)
83
+    user_id = marshmallow.fields.Int(
84
+        example=3,
85
+        required=True,
86
+        description='id of a valid user',
87
+        validate=Range(min=1, error="Value must be greater than 0"),
88
+    )
84
 
89
 
85
 
90
 
86
 class WorkspaceIdPathSchema(marshmallow.Schema):
91
 class WorkspaceIdPathSchema(marshmallow.Schema):
87
-    workspace_id = marshmallow.fields.Int(example=4, required=True)
92
+    workspace_id = marshmallow.fields.Int(
93
+        example=4,
94
+        required=True,
95
+        description='id of a valid workspace',
96
+        validate=Range(min=1, error="Value must be greater than 0"),
97
+    )
88
 
98
 
89
 
99
 
90
 class ContentIdPathSchema(marshmallow.Schema):
100
 class ContentIdPathSchema(marshmallow.Schema):
91
-    content_id = marshmallow.fields.Int(example=6, required=True)
101
+    content_id = marshmallow.fields.Int(
102
+        example=6,
103
+        required=True,
104
+        description='id of a valid content',
105
+        validate=Range(min=1, error="Value must be greater than 0"),
106
+    )
92
 
107
 
93
 
108
 
94
 class WorkspaceAndContentIdPathSchema(
109
 class WorkspaceAndContentIdPathSchema(
103
 class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
118
 class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
104
     comment_id = marshmallow.fields.Int(
119
     comment_id = marshmallow.fields.Int(
105
         example=6,
120
         example=6,
106
-        description='id of a comment related to content content_id',
107
-        required=True
121
+        description='id of a valid comment related to content content_id',
122
+        required=True,
123
+        validate=Range(min=1, error="Value must be greater than 0"),
108
     )
124
     )
109
     @post_load
125
     @post_load
110
     def make_path_object(self, data):
126
     def make_path_object(self, data):
119
                     ' If not set, then return all contents.'
135
                     ' If not set, then return all contents.'
120
                     ' If set to 0, then return root contents.'
136
                     ' If set to 0, then return root contents.'
121
                     ' If set to another value, return all contents'
137
                     ' If set to another value, return all contents'
122
-                    ' directly included in the folder parent_id'
138
+                    ' directly included in the folder parent_id',
139
+        validate=Range(min=0, error="Value must be positive or 0"),
123
     )
140
     )
124
     show_archived = marshmallow.fields.Int(
141
     show_archived = marshmallow.fields.Int(
125
         example=0,
142
         example=0,
126
         default=0,
143
         default=0,
127
         description='if set to 1, then show archived contents.'
144
         description='if set to 1, then show archived contents.'
128
-                    ' Default is 0 - hide archived content'
145
+                    ' Default is 0 - hide archived content',
146
+        validate=Range(min=0, max=1, error="Value must be 0 or 1"),
129
     )
147
     )
130
     show_deleted = marshmallow.fields.Int(
148
     show_deleted = marshmallow.fields.Int(
131
         example=0,
149
         example=0,
132
         default=0,
150
         default=0,
133
         description='if set to 1, then show deleted contents.'
151
         description='if set to 1, then show deleted contents.'
134
-                    ' Default is 0 - hide deleted content'
152
+                    ' Default is 0 - hide deleted content',
153
+        validate=Range(min=0, max=1, error="Value must be 0 or 1"),
135
     )
154
     )
136
     show_active = marshmallow.fields.Int(
155
     show_active = marshmallow.fields.Int(
137
         example=1,
156
         example=1,
141
                     ' Note: active content are content '
160
                     ' Note: active content are content '
142
                     'that is neither archived nor deleted. '
161
                     'that is neither archived nor deleted. '
143
                     'The reason for this parameter to exist is for example '
162
                     'The reason for this parameter to exist is for example '
144
-                    'to allow to show only archived documents'
163
+                    'to allow to show only archived documents',
164
+        validate=Range(min=0, max=1, error="Value must be 0 or 1"),
145
     )
165
     )
146
 
166
 
147
     @post_load
167
     @post_load
205
 
225
 
206
 
226
 
207
 class WorkspaceDigestSchema(marshmallow.Schema):
227
 class WorkspaceDigestSchema(marshmallow.Schema):
208
-    workspace_id = marshmallow.fields.Int(example=4)
228
+    workspace_id = marshmallow.fields.Int(
229
+        example=4,
230
+        validate=Range(min=1, error="Value must be greater than 0"),
231
+    )
209
     slug = marshmallow.fields.String(example='intranet')
232
     slug = marshmallow.fields.String(example='intranet')
210
     label = marshmallow.fields.String(example='Intranet')
233
     label = marshmallow.fields.String(example='Intranet')
211
     sidebar_entries = marshmallow.fields.Nested(
234
     sidebar_entries = marshmallow.fields.Nested(
229
         example='contributor',
252
         example='contributor',
230
         validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
253
         validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
231
     )
254
     )
232
-    user_id = marshmallow.fields.Int(example=3)
233
-    workspace_id = marshmallow.fields.Int(example=4)
255
+    user_id = marshmallow.fields.Int(
256
+        example=3,
257
+        validate=Range(min=1, error="Value must be greater than 0"),
258
+    )
259
+    workspace_id = marshmallow.fields.Int(
260
+        example=4,
261
+        validate=Range(min=1, error="Value must be greater than 0"),
262
+    )
234
     user = marshmallow.fields.Nested(
263
     user = marshmallow.fields.Nested(
235
         UserSchema(only=('public_name', 'avatar_url'))
264
         UserSchema(only=('public_name', 'avatar_url'))
236
     )
265
     )
286
 class ContentTypeSchema(marshmallow.Schema):
315
 class ContentTypeSchema(marshmallow.Schema):
287
     slug = marshmallow.fields.String(
316
     slug = marshmallow.fields.String(
288
         example='pagehtml',
317
         example='pagehtml',
289
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
318
+        validate=OneOf(ContentType.allowed_types()),
290
     )
319
     )
291
     fa_icon = marshmallow.fields.String(
320
     fa_icon = marshmallow.fields.String(
292
         example='fa-file-text-o',
321
         example='fa-file-text-o',
319
         description='id of the new parent content id.',
348
         description='id of the new parent content id.',
320
         allow_none=True,
349
         allow_none=True,
321
         required=True,
350
         required=True,
351
+        validate=Range(min=0, error="Value must be positive or 0"),
322
     )
352
     )
323
     new_workspace_id = marshmallow.fields.Int(
353
     new_workspace_id = marshmallow.fields.Int(
324
         example=2,
354
         example=2,
325
         description='id of the new workspace id.',
355
         description='id of the new workspace id.',
326
-        required=True
356
+        required=True,
357
+        validate=Range(min=1, error="Value must be greater than 0"),
327
     )
358
     )
328
 
359
 
329
     @post_load
360
     @post_load
338
     )
369
     )
339
     content_type = marshmallow.fields.String(
370
     content_type = marshmallow.fields.String(
340
         example='html-documents',
371
         example='html-documents',
341
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
372
+        validate=OneOf(ContentType.allowed_types_for_folding()),  # nopep8
342
     )
373
     )
343
 
374
 
344
     @post_load
375
     @post_load
347
 
378
 
348
 
379
 
349
 class ContentDigestSchema(marshmallow.Schema):
380
 class ContentDigestSchema(marshmallow.Schema):
350
-    content_id = marshmallow.fields.Int(example=6)
381
+    content_id = marshmallow.fields.Int(
382
+        example=6,
383
+        validate=Range(min=1, error="Value must be greater than 0"),
384
+    )
351
     slug = marshmallow.fields.Str(example='intervention-report-12')
385
     slug = marshmallow.fields.Str(example='intervention-report-12')
352
     parent_id = marshmallow.fields.Int(
386
     parent_id = marshmallow.fields.Int(
353
         example=34,
387
         example=34,
354
         allow_none=True,
388
         allow_none=True,
355
-        default=None
389
+        default=None,
390
+        validate=Range(min=0, error="Value must be positive or 0"),
356
     )
391
     )
357
     workspace_id = marshmallow.fields.Int(
392
     workspace_id = marshmallow.fields.Int(
358
         example=19,
393
         example=19,
394
+        validate=Range(min=1, error="Value must be greater than 0"),
359
     )
395
     )
360
     label = marshmallow.fields.Str(example='Intervention Report 12')
396
     label = marshmallow.fields.Str(example='Intervention Report 12')
361
     content_type = marshmallow.fields.Str(
397
     content_type = marshmallow.fields.Str(
362
         example='html-documents',
398
         example='html-documents',
363
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
399
+        validate=OneOf(ContentType.allowed_types()),
364
     )
400
     )
365
     sub_content_types = marshmallow.fields.List(
401
     sub_content_types = marshmallow.fields.List(
366
-        marshmallow.fields.String(),
402
+        marshmallow.fields.String(
403
+            example='html-content',
404
+            validate=OneOf(ContentType.allowed_types())
405
+        ),
367
         description='list of content types allowed as sub contents. '
406
         description='list of content types allowed as sub contents. '
368
                     'This field is required for folder contents, '
407
                     'This field is required for folder contents, '
369
                     'set it to empty list in other cases'
408
                     'set it to empty list in other cases'
370
     )
409
     )
371
     status = marshmallow.fields.Str(
410
     status = marshmallow.fields.Str(
372
         example='closed-deprecated',
411
         example='closed-deprecated',
373
-        validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
412
+        validate=OneOf(ContentStatus.allowed_values()),
374
         description='this slug is found in content_type available statuses',
413
         description='this slug is found in content_type available statuses',
375
         default=open_status
414
         default=open_status
376
     )
415
     )
403
     last_modifier = marshmallow.fields.Nested(UserDigestSchema)
442
     last_modifier = marshmallow.fields.Nested(UserDigestSchema)
404
 
443
 
405
 
444
 
406
-class ThreadContentSchema(ContentSchema):
407
-    content_type = marshmallow.fields.Str(
408
-        example='thread',
409
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
445
+class TextBasedDataAbstractSchema(marshmallow.Schema):
446
+    raw_content = marshmallow.fields.String(
447
+        description='Content of the object, may be raw text or <b>html</b> for example'  # nopep8
410
     )
448
     )
411
-    raw_content = marshmallow.fields.String('Description of Thread')
412
 
449
 
413
 
450
 
414
-class HtmlDocumentContentSchema(ContentSchema):
415
-    content_type = marshmallow.fields.Str(
416
-        example='html-documents',
417
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
418
-    )
419
-    raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
451
+class TextBasedContentSchema(ContentSchema, TextBasedDataAbstractSchema):
452
+    pass
453
+
420
 
454
 
421
 #####
455
 #####
422
 # Revision
456
 # Revision
424
 
458
 
425
 
459
 
426
 class RevisionSchema(ContentDigestSchema):
460
 class RevisionSchema(ContentDigestSchema):
427
-    comment_ids = marshmallow.fields.List(marshmallow.fields.Int(example=4))
428
-    revision_id = marshmallow.fields.Int(example=12)
461
+    comment_ids = marshmallow.fields.List(
462
+        marshmallow.fields.Int(
463
+            example=4,
464
+            validate=Range(min=1, error="Value must be greater than 0"),
465
+        )
466
+    )
467
+    revision_id = marshmallow.fields.Int(
468
+        example=12,
469
+        validate=Range(min=1, error="Value must be greater than 0"),
470
+    )
429
     created = marshmallow.fields.DateTime(
471
     created = marshmallow.fields.DateTime(
430
         format=DATETIME_FORMAT,
472
         format=DATETIME_FORMAT,
431
         description='Content creation date',
473
         description='Content creation date',
433
     author = marshmallow.fields.Nested(UserDigestSchema)
475
     author = marshmallow.fields.Nested(UserDigestSchema)
434
 
476
 
435
 
477
 
436
-class ThreadRevisionSchema(RevisionSchema):
437
-    content_type = marshmallow.fields.Str(
438
-        example='thread',
439
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
440
-    )
441
-    raw_content = marshmallow.fields.String('Description of Thread')
442
-
443
-
444
-class HtmlDocumentRevisionSchema(RevisionSchema):
445
-    content_type = marshmallow.fields.Str(
446
-        example='html-documents',
447
-        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
448
-    )
449
-    raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
450
-
478
+class TextBasedRevisionSchema(RevisionSchema, TextBasedDataAbstractSchema):
479
+    pass
451
 
480
 
452
-####
453
 
481
 
454
 class CommentSchema(marshmallow.Schema):
482
 class CommentSchema(marshmallow.Schema):
455
-    content_id = marshmallow.fields.Int(example=6)
456
-    parent_id = marshmallow.fields.Int(example=34)
483
+    content_id = marshmallow.fields.Int(
484
+        example=6,
485
+        validate=Range(min=1, error="Value must be greater than 0"),
486
+    )
487
+    parent_id = marshmallow.fields.Int(
488
+        example=34,
489
+        validate=Range(min=0, error="Value must be positive or 0"),
490
+    )
457
     raw_content = marshmallow.fields.String(
491
     raw_content = marshmallow.fields.String(
458
         example='<p>This is just an html comment !</p>'
492
         example='<p>This is just an html comment !</p>'
459
     )
493
     )
464
     )
498
     )
465
 
499
 
466
 
500
 
467
-class ContentModifySchema(marshmallow.Schema):
468
-    label = marshmallow.fields.String(
469
-        example='contract for client XXX',
470
-        description='New title of the content'
501
+class SetCommentSchema(marshmallow.Schema):
502
+    raw_content = marshmallow.fields.String(
503
+        example='<p>This is just an html comment !</p>'
471
     )
504
     )
472
 
505
 
506
+    @post_load()
507
+    def create_comment(self, data):
508
+        return CommentCreation(**data)
473
 
509
 
474
-class HtmlDocumentModifySchema(ContentModifySchema):
475
-    raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
476
-
477
-    @post_load
478
-    def html_document_update(self, data):
479
-        return HTMLDocumentUpdate(**data)
480
-
481
-
482
-class ThreadModifySchema(ContentModifySchema):
483
-    raw_content = marshmallow.fields.String('Description of Thread')
484
 
510
 
485
-    @post_load
486
-    def thread_update(self, data):
487
-        return ThreadUpdate(**data)
511
+class ContentModifyAbstractSchema(marshmallow.Schema):
512
+    label = marshmallow.fields.String(
513
+        required=True,
514
+        example='contract for client XXX',
515
+        description='New title of the content'
516
+    )
488
 
517
 
489
 
518
 
490
-class SetCommentSchema(marshmallow.Schema):
491
-    raw_content = marshmallow.fields.String(
492
-        example='<p>This is just an html comment !</p>'
493
-    )
519
+class TextBasedContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbstractSchema):  # nopep8
494
 
520
 
495
     @post_load
521
     @post_load
496
-    def create_comment(self, data):
497
-        return CommentCreation(**data)
522
+    def text_based_content_update(self, data):
523
+        return TextBasedContentUpdate(**data)
498
 
524
 
499
 
525
 
500
 class SetContentStatusSchema(marshmallow.Schema):
526
 class SetContentStatusSchema(marshmallow.Schema):
501
     status = marshmallow.fields.Str(
527
     status = marshmallow.fields.Str(
502
         example='closed-deprecated',
528
         example='closed-deprecated',
503
-        validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
529
+        validate=OneOf(ContentStatus.allowed_values()),
504
         description='this slug is found in content_type available statuses',
530
         description='this slug is found in content_type available statuses',
505
         default=open_status,
531
         default=open_status,
506
         required=True,
532
         required=True,

+ 0 - 3
tracim/views/core_api/session_controller.py Целия файл

24
     @hapic.with_api_doc(tags=[SESSION_ENDPOINTS_TAG])
24
     @hapic.with_api_doc(tags=[SESSION_ENDPOINTS_TAG])
25
     @hapic.input_headers(LoginOutputHeaders())
25
     @hapic.input_headers(LoginOutputHeaders())
26
     @hapic.input_body(BasicAuthSchema())
26
     @hapic.input_body(BasicAuthSchema())
27
-    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
28
     # TODO - G.M - 17-04-2018 - fix output header ?
27
     # TODO - G.M - 17-04-2018 - fix output header ?
29
     # @hapic.output_headers()
28
     # @hapic.output_headers()
30
     @hapic.output_body(UserSchema(),)
29
     @hapic.output_body(UserSchema(),)
31
-    #@hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
32
     def login(self, context, request: TracimRequest, hapic_data=None):
30
     def login(self, context, request: TracimRequest, hapic_data=None):
33
         """
31
         """
34
         Logs user into the system
32
         Logs user into the system
54
         return
52
         return
55
 
53
 
56
     @hapic.with_api_doc(tags=[SESSION_ENDPOINTS_TAG])
54
     @hapic.with_api_doc(tags=[SESSION_ENDPOINTS_TAG])
57
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
58
     @hapic.output_body(UserSchema(),)
55
     @hapic.output_body(UserSchema(),)
59
     def whoami(self, context, request: TracimRequest, hapic_data=None):
56
     def whoami(self, context, request: TracimRequest, hapic_data=None):
60
         """
57
         """

+ 5 - 7
tracim/views/core_api/system_controller.py Целия файл

5
 from tracim.lib.utils.authorization import require_profile
5
 from tracim.lib.utils.authorization import require_profile
6
 from tracim.models import Group
6
 from tracim.models import Group
7
 from tracim.models.applications import applications
7
 from tracim.models.applications import applications
8
-from tracim.models.contents import CONTENT_DEFAULT_TYPE
8
+from tracim.models.contents import ContentTypeLegacy as ContentType
9
 
9
 
10
 try:  # Python 3.5+
10
 try:  # Python 3.5+
11
     from http import HTTPStatus
11
     from http import HTTPStatus
20
 
20
 
21
 SYSTEM_ENDPOINTS_TAG = 'System'
21
 SYSTEM_ENDPOINTS_TAG = 'System'
22
 
22
 
23
+
23
 class SystemController(Controller):
24
 class SystemController(Controller):
24
 
25
 
25
     @hapic.with_api_doc(tags=[SYSTEM_ENDPOINTS_TAG])
26
     @hapic.with_api_doc(tags=[SYSTEM_ENDPOINTS_TAG])
26
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
27
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
28
     @require_profile(Group.TIM_USER)
27
     @require_profile(Group.TIM_USER)
29
     @hapic.output_body(ApplicationSchema(many=True),)
28
     @hapic.output_body(ApplicationSchema(many=True),)
30
     def applications(self, context, request: TracimRequest, hapic_data=None):
29
     def applications(self, context, request: TracimRequest, hapic_data=None):
34
         return applications
33
         return applications
35
 
34
 
36
     @hapic.with_api_doc(tags=[SYSTEM_ENDPOINTS_TAG])
35
     @hapic.with_api_doc(tags=[SYSTEM_ENDPOINTS_TAG])
37
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
38
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
39
     @require_profile(Group.TIM_USER)
36
     @require_profile(Group.TIM_USER)
40
     @hapic.output_body(ContentTypeSchema(many=True),)
37
     @hapic.output_body(ContentTypeSchema(many=True),)
41
     def content_types(self, context, request: TracimRequest, hapic_data=None):
38
     def content_types(self, context, request: TracimRequest, hapic_data=None):
42
         """
39
         """
43
         Get list of alls content types availables in this tracim instance.
40
         Get list of alls content types availables in this tracim instance.
44
         """
41
         """
45
-
46
-        return CONTENT_DEFAULT_TYPE
42
+        content_types_slugs = ContentType.allowed_types_for_folding()
43
+        content_types = [ContentType(slug) for slug in content_types_slugs]
44
+        return content_types
47
 
45
 
48
     def bind(self, configurator: Configurator) -> None:
46
     def bind(self, configurator: Configurator) -> None:
49
         """
47
         """

+ 0 - 3
tracim/views/core_api/user_controller.py Целия файл

26
 class UserController(Controller):
26
 class UserController(Controller):
27
 
27
 
28
     @hapic.with_api_doc(tags=[USER_ENDPOINTS_TAG])
28
     @hapic.with_api_doc(tags=[USER_ENDPOINTS_TAG])
29
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
30
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
31
-    @hapic.handle_exception(UserDoesNotExist, HTTPStatus.NOT_FOUND)
32
     @require_same_user_or_profile(Group.TIM_ADMIN)
29
     @require_same_user_or_profile(Group.TIM_ADMIN)
33
     @hapic.input_path(UserIdPathSchema())
30
     @hapic.input_path(UserIdPathSchema())
34
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
31
     @hapic.output_body(WorkspaceDigestSchema(many=True),)

+ 5 - 33
tracim/views/core_api/workspace_controller.py Целия файл

11
 from tracim.lib.core.workspace import WorkspaceApi
11
 from tracim.lib.core.workspace import WorkspaceApi
12
 from tracim.lib.core.content import ContentApi
12
 from tracim.lib.core.content import ContentApi
13
 from tracim.lib.core.userworkspace import RoleApi
13
 from tracim.lib.core.userworkspace import RoleApi
14
-from tracim.lib.utils.authorization import require_workspace_role, \
15
-    require_candidate_workspace_role
14
+from tracim.lib.utils.authorization import require_workspace_role
15
+from tracim.lib.utils.authorization import require_candidate_workspace_role
16
 from tracim.models.data import UserRoleInWorkspace
16
 from tracim.models.data import UserRoleInWorkspace
17
 from tracim.models.data import ActionDescription
17
 from tracim.models.data import ActionDescription
18
 from tracim.models.context_models import UserRoleWorkspaceInContext
18
 from tracim.models.context_models import UserRoleWorkspaceInContext
19
 from tracim.models.context_models import ContentInContext
19
 from tracim.models.context_models import ContentInContext
20
-from tracim.exceptions import NotAuthenticated, InsufficientUserRoleInWorkspace
21
-from tracim.exceptions import WorkspaceNotFoundInTracimRequest
20
+from tracim.exceptions import EmptyLabelNotAllowed
22
 from tracim.exceptions import WorkspacesDoNotMatch
21
 from tracim.exceptions import WorkspacesDoNotMatch
23
-from tracim.exceptions import WorkspaceNotFound
24
 from tracim.views.controllers import Controller
22
 from tracim.views.controllers import Controller
25
 from tracim.views.core_api.schemas import FilterContentQuerySchema
23
 from tracim.views.core_api.schemas import FilterContentQuerySchema
26
 from tracim.views.core_api.schemas import ContentMoveSchema
24
 from tracim.views.core_api.schemas import ContentMoveSchema
40
 class WorkspaceController(Controller):
38
 class WorkspaceController(Controller):
41
 
39
 
42
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
40
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
43
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
44
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
45
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
46
     @require_workspace_role(UserRoleInWorkspace.READER)
41
     @require_workspace_role(UserRoleInWorkspace.READER)
47
     @hapic.input_path(WorkspaceIdPathSchema())
42
     @hapic.input_path(WorkspaceIdPathSchema())
48
     @hapic.output_body(WorkspaceSchema())
43
     @hapic.output_body(WorkspaceSchema())
60
         return wapi.get_workspace_with_context(request.current_workspace)
55
         return wapi.get_workspace_with_context(request.current_workspace)
61
 
56
 
62
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
57
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
63
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
64
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
65
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
66
     @require_workspace_role(UserRoleInWorkspace.READER)
58
     @require_workspace_role(UserRoleInWorkspace.READER)
67
     @hapic.input_path(WorkspaceIdPathSchema())
59
     @hapic.input_path(WorkspaceIdPathSchema())
68
     @hapic.output_body(WorkspaceMemberSchema(many=True))
60
     @hapic.output_body(WorkspaceMemberSchema(many=True))
89
         ]
81
         ]
90
 
82
 
91
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
83
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
92
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
93
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
94
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
95
     @require_workspace_role(UserRoleInWorkspace.READER)
84
     @require_workspace_role(UserRoleInWorkspace.READER)
96
     @hapic.input_path(WorkspaceIdPathSchema())
85
     @hapic.input_path(WorkspaceIdPathSchema())
97
     @hapic.input_query(FilterContentQuerySchema())
86
     @hapic.input_query(FilterContentQuerySchema())
125
         return contents
114
         return contents
126
 
115
 
127
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
116
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
128
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
129
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
130
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
131
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
117
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
118
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
132
     @hapic.input_path(WorkspaceIdPathSchema())
119
     @hapic.input_path(WorkspaceIdPathSchema())
133
     @hapic.input_body(ContentCreationSchema())
120
     @hapic.input_body(ContentCreationSchema())
134
     @hapic.output_body(ContentDigestSchema())
121
     @hapic.output_body(ContentDigestSchema())
137
             context,
124
             context,
138
             request: TracimRequest,
125
             request: TracimRequest,
139
             hapic_data=None,
126
             hapic_data=None,
140
-    ) -> typing.List[ContentInContext]:
127
+    ) -> ContentInContext:
141
         """
128
         """
142
         create a generic empty content
129
         create a generic empty content
143
         """
130
         """
158
         return content
145
         return content
159
 
146
 
160
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
147
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
161
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
162
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
163
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
164
     @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST)
148
     @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST)
165
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
149
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
166
     @require_candidate_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
150
     @require_candidate_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
213
         return api.get_content_in_context(updated_content)
197
         return api.get_content_in_context(updated_content)
214
 
198
 
215
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
199
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
216
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
217
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
218
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
219
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
200
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
220
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
201
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
221
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
202
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
248
         return
229
         return
249
 
230
 
250
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
231
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
251
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
252
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
253
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
254
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
232
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
255
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
233
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
256
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
234
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
284
         return
262
         return
285
 
263
 
286
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
264
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
287
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
288
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
289
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
290
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
265
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
291
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
266
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
292
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
267
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
316
         return
291
         return
317
 
292
 
318
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
293
     @hapic.with_api_doc(tags=[WORKSPACE_ENDPOINTS_TAG])
319
-    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
320
-    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
321
-    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
322
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
294
     @require_workspace_role(UserRoleInWorkspace.CONTENT_MANAGER)
323
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
295
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
324
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
296
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8