瀏覽代碼

add endpoint and tests for comments of contents

Guénaël Muller 6 年之前
父節點
當前提交
9baf28fcdd

+ 3 - 0
tracim/__init__.py 查看文件

21
 from tracim.views.core_api.system_controller import SystemController
21
 from tracim.views.core_api.system_controller import SystemController
22
 from tracim.views.core_api.user_controller import UserController
22
 from tracim.views.core_api.user_controller import UserController
23
 from tracim.views.core_api.workspace_controller import WorkspaceController
23
 from tracim.views.core_api.workspace_controller import WorkspaceController
24
+from tracim.views.contents_api.comment_controller import CommentController
24
 from tracim.views.errors import ErrorSchema
25
 from tracim.views.errors import ErrorSchema
25
 from tracim.lib.utils.cors import add_cors_support
26
 from tracim.lib.utils.cors import add_cors_support
26
 
27
 
71
     system_controller = SystemController()
72
     system_controller = SystemController()
72
     user_controller = UserController()
73
     user_controller = UserController()
73
     workspace_controller = WorkspaceController()
74
     workspace_controller = WorkspaceController()
75
+    comment_controller = CommentController()
74
     configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
76
     configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
75
     configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
77
     configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
76
     configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
78
     configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
77
     configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
79
     configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
80
+    configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
78
     hapic.add_documentation_view(
81
     hapic.add_documentation_view(
79
         '/api/v2/doc',
82
         '/api/v2/doc',
80
         'Tracim v2 API',
83
         'Tracim v2 API',

+ 20 - 1
tracim/fixtures/content.py 查看文件

38
             session=self._session,
38
             session=self._session,
39
             config=self._config
39
             config=self._config
40
         )
40
         )
41
+        bob_content_api = ContentApi(
42
+            current_user=bob,
43
+            session=self._session,
44
+            config=self._config
45
+        )
41
         role_api = RoleApi(
46
         role_api = RoleApi(
42
             current_user=admin,
47
             current_user=admin,
43
             session=self._session,
48
             session=self._session,
246
             content_api.delete(bad_fruit_salad)
251
             content_api.delete(bad_fruit_salad)
247
         content_api.save(bad_fruit_salad)
252
         content_api.save(bad_fruit_salad)
248
 
253
 
249
-
254
+        content_api.create_comment(
255
+            parent=best_cake_thread,
256
+            content='<p> What is for you the best cake ever ? </br> I personnally vote for Chocolate cupcake !</p>',  # nopep8
257
+            do_save=True,
258
+        )
259
+        bob_content_api.create_comment(
260
+            parent=best_cake_thread,
261
+            content='<p>What about Apple Pie ? There are Awesome !</p>',
262
+            do_save=True,
263
+        )
264
+        content_api.create_comment(
265
+            parent=best_cake_thread,
266
+            content='<p>You are right, but Kouign-amann are clearly better.</p>',
267
+            do_save=True,
268
+        )
250
         self._session.flush()
269
         self._session.flush()

+ 11 - 4
tracim/lib/core/content.py 查看文件

418
         item = Content()
418
         item = Content()
419
         item.owner = self._user
419
         item.owner = self._user
420
         item.parent = parent
420
         item.parent = parent
421
+        if parent and not workspace:
422
+            workspace = item.parent.workspace
421
         item.workspace = workspace
423
         item.workspace = workspace
422
         item.type = ContentType.Comment
424
         item.type = ContentType.Comment
423
         item.description = content
425
         item.description = content
449
 
451
 
450
         return content
452
         return content
451
 
453
 
452
-    def get_one(self, content_id: int, content_type: str, workspace: Workspace=None) -> Content:
454
+    def get_one(self, content_id: int, content_type: str, workspace: Workspace=None, parent: Content=None) -> Content:
453
 
455
 
454
         if not content_id:
456
         if not content_id:
455
             return None
457
             return None
456
 
458
 
457
-        if content_type==ContentType.Any:
458
-            return self._base_query(workspace).filter(Content.content_id==content_id).one()
459
+        base_request = self._base_query(workspace).filter(Content.content_id==content_id)
459
 
460
 
460
-        return self._base_query(workspace).filter(Content.content_id==content_id).filter(Content.type==content_type).one()
461
+        if content_type!=ContentType.Any:
462
+            base_request = base_request.filter(Content.type==content_type)
463
+
464
+        if parent:
465
+            base_request = base_request.filter(Content.parent_id==parent.content_id)  # nopep8
466
+
467
+        return base_request.one()
461
 
468
 
462
     def get_one_revision(self, revision_id: int = None) -> ContentRevisionRO:
469
     def get_one_revision(self, revision_id: int = None) -> ContentRevisionRO:
463
         """
470
         """

+ 29 - 4
tracim/models/contents.py 查看文件

186
     folder_type,
186
     folder_type,
187
 ]
187
 ]
188
 
188
 
189
+# TODO - G.M - 31-05-2018 - Set Better Event params
190
+event_type = NewContentType(
191
+    slug='event',
192
+    fa_icon=thread.fa_icon,
193
+    hexcolor=thread.hexcolor,
194
+    label='Event',
195
+    creation_label='Event',
196
+    available_statuses=CONTENT_DEFAULT_STATUS,
197
+)
198
+
199
+# TODO - G.M - 31-05-2018 - Set Better Event params
200
+comment_type = NewContentType(
201
+    slug='comment',
202
+    fa_icon=thread.fa_icon,
203
+    hexcolor=thread.hexcolor,
204
+    label='Comment',
205
+    creation_label='Comment',
206
+    available_statuses=CONTENT_DEFAULT_STATUS,
207
+)
208
+
209
+CONTENT_DEFAULT_TYPE_SPECIAL = [
210
+    event_type,
211
+    comment_type,
212
+]
213
+
214
+ALL_CONTENTS_DEFAULT_TYPES = CONTENT_DEFAULT_TYPE + CONTENT_DEFAULT_TYPE_SPECIAL
215
+
189
 
216
 
190
 class ContentTypeLegacy(NewContentType):
217
 class ContentTypeLegacy(NewContentType):
191
     """
218
     """
204
     MarkdownPage = markdownpluspage_type.slug
231
     MarkdownPage = markdownpluspage_type.slug
205
 
232
 
206
     def __init__(self, slug: str):
233
     def __init__(self, slug: str):
207
-        for content_type in CONTENT_DEFAULT_TYPE:
234
+        for content_type in ALL_CONTENTS_DEFAULT_TYPES:
208
             if slug == content_type.slug:
235
             if slug == content_type.slug:
209
                 super(ContentTypeLegacy, self).__init__(
236
                 super(ContentTypeLegacy, self).__init__(
210
                     slug=content_type.slug,
237
                     slug=content_type.slug,
223
 
250
 
224
     @classmethod
251
     @classmethod
225
     def allowed_types(cls) -> typing.List[str]:
252
     def allowed_types(cls) -> typing.List[str]:
226
-        contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
227
-        contents_types.extend([cls.Folder, cls.Event, cls.Comment])
253
+        contents_types = [status.slug for status in ALL_CONTENTS_DEFAULT_TYPES]
228
         return contents_types
254
         return contents_types
229
 
255
 
230
     @classmethod
256
     @classmethod
232
         # This method is used for showing only "main"
258
         # This method is used for showing only "main"
233
         # types in the left-side treeview
259
         # types in the left-side treeview
234
         contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
260
         contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
235
-        contents_types.extend([cls.Folder])
236
         return contents_types
261
         return contents_types
237
 
262
 
238
     # TODO - G.M - 30-05-2018 - This method don't do anything.
263
     # TODO - G.M - 30-05-2018 - This method don't do anything.

+ 33 - 0
tracim/models/context_models.py 查看文件

41
         self.workspace_id = workspace_id
41
         self.workspace_id = workspace_id
42
 
42
 
43
 
43
 
44
+class CommentPath(object):
45
+    """
46
+    Paths params with workspace id and content_id
47
+    """
48
+    def __init__(self, workspace_id: int, content_id: int, comment_id: int):
49
+        self.content_id = content_id
50
+        self.workspace_id = workspace_id
51
+        self.comment_id = comment_id
52
+
53
+
44
 class ContentFilter(object):
54
 class ContentFilter(object):
45
     """
55
     """
46
     Content filter model
56
     Content filter model
71
         self.content_type = content_type
81
         self.content_type = content_type
72
 
82
 
73
 
83
 
84
+class CommentCreation(object):
85
+    """
86
+    Comment creation model
87
+    """
88
+    def __init__(
89
+            self,
90
+            raw_content: str,
91
+    ):
92
+        self.raw_content=raw_content
93
+
94
+
74
 class UserInContext(object):
95
 class UserInContext(object):
75
     """
96
     """
76
     Interface to get User data and User data related to context.
97
     Interface to get User data and User data related to context.
324
     def is_deleted(self):
345
     def is_deleted(self):
325
         return self.content.is_deleted
346
         return self.content.is_deleted
326
 
347
 
348
+    @property
349
+    def raw_content(self):
350
+        return self.content.description
351
+
352
+    @property
353
+    def author(self):
354
+        return UserInContext(
355
+            dbsession=self.dbsession,
356
+            config=self.config,
357
+            user=self.content.owner
358
+        )
359
+
327
     # Context-related
360
     # Context-related
328
 
361
 
329
     @property
362
     @property

+ 123 - 0
tracim/tests/functional/test_comments.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+"""
3
+Tests for /api/v2/workspaces subpath endpoints.
4
+"""
5
+from tracim.tests import FunctionalTest
6
+from tracim.fixtures.content import Content as ContentFixtures
7
+from tracim.fixtures.users_and_groups import Base as BaseFixture
8
+
9
+
10
+class TestCommentsEndpoint(FunctionalTest):
11
+    """
12
+    Tests for /api/v2/workspaces/{workspace_id}/contents/{content_id}/comments
13
+    endpoint
14
+    """
15
+
16
+    fixtures = [BaseFixture, ContentFixtures]
17
+
18
+    def test_api__get_contents_comments__ok_200__nominal_case(self) -> None:
19
+        """
20
+        Get alls comments of a content
21
+        """
22
+        self.testapp.authorization = (
23
+            'Basic',
24
+            (
25
+                'admin@admin.admin',
26
+                'admin@admin.admin'
27
+            )
28
+        )
29
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)   # nopep8
30
+        assert len(res.json_body) == 3
31
+        comment = res.json_body[0]
32
+        assert comment['content_id'] == 18
33
+        assert comment['parent_id'] == 7
34
+        assert comment['raw_content'] == '<p> What is for you the best cake ever ? </br> I personnally vote for Chocolate cupcake !</p>'  # nopep8
35
+        assert comment['author']
36
+        assert comment['author']['user_id'] == 1
37
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
38
+        assert comment['author']['avatar_url'] == None
39
+        assert comment['author']['public_name'] == 'Global manager'
40
+
41
+        comment = res.json_body[1]
42
+        assert comment['content_id'] == 19
43
+        assert comment['parent_id'] == 7
44
+        assert comment['raw_content'] == '<p>What about Apple Pie ? There are Awesome !</p>'  # nopep8
45
+        assert comment['author']
46
+        assert comment['author']['user_id'] == 3
47
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
48
+        assert comment['author']['avatar_url'] == None
49
+        assert comment['author']['public_name'] == 'Bob i.'
50
+
51
+        comment = res.json_body[2]
52
+        assert comment['content_id'] == 20
53
+        assert comment['parent_id'] == 7
54
+        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'  # nopep8
55
+        assert comment['author']
56
+        assert comment['author']['user_id'] == 1
57
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
58
+        assert comment['author']['avatar_url'] == None
59
+        assert comment['author']['public_name'] == 'Global manager'
60
+
61
+    def test_api__post_content_comment__ok_200__nominal_case(self) -> None:
62
+        """
63
+        Get alls comments of a content
64
+        """
65
+        self.testapp.authorization = (
66
+            'Basic',
67
+            (
68
+                'admin@admin.admin',
69
+                'admin@admin.admin'
70
+            )
71
+        )
72
+        params = {
73
+            'raw_content': 'I strongly disagree, Tiramisu win !'
74
+        }
75
+        res = self.testapp.post_json(
76
+            '/api/v2/workspaces/2/contents/7/comments',
77
+            params=params,
78
+            status=200
79
+        )
80
+        comment = res.json_body
81
+        assert comment['content_id']
82
+        assert comment['parent_id'] == 7
83
+        assert comment['raw_content'] == 'I strongly disagree, Tiramisu win !'
84
+        assert comment['author']
85
+        assert comment['author']['user_id'] == 1
86
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
87
+        assert comment['author']['avatar_url'] is None
88
+        assert comment['author']['public_name'] == 'Global manager'
89
+
90
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)  # nopep8
91
+        assert len(res.json_body) == 4
92
+        assert comment == res.json_body[3]
93
+
94
+    def test_api__delete_content_comment__ok_200__nominal_case(self) -> None:
95
+        """
96
+        Get alls comments of a content
97
+        """
98
+        self.testapp.authorization = (
99
+            'Basic',
100
+            (
101
+                'admin@admin.admin',
102
+                'admin@admin.admin'
103
+            )
104
+        )
105
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
106
+        assert len(res.json_body) == 3
107
+        comment = res.json_body[2]
108
+        assert comment['content_id'] == 20
109
+        assert comment['parent_id'] == 7
110
+        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'   # nopep8
111
+        assert comment['author']
112
+        assert comment['author']['user_id'] == 1
113
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
114
+        assert comment['author']['avatar_url'] is None
115
+        assert comment['author']['public_name'] == 'Global manager'
116
+
117
+        res = self.testapp.delete(
118
+            '/api/v2/workspaces/2/contents/7/comments/20',
119
+            status=204
120
+        )
121
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
122
+        assert len(res.json_body) == 2
123
+        assert not [content for content in res.json_body if content['content_id'] == 20]  # nopep8

+ 4 - 4
tracim/tests/functional/test_mail_notification.py 查看文件

162
         assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>'  # nopep8
162
         assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>'  # nopep8
163
         assert headers['To'][0] == 'Global manager <admin@admin.admin>'
163
         assert headers['To'][0] == 'Global manager <admin@admin.admin>'
164
         assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)'
164
         assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)'
165
-        assert headers['References'][0] == 'test_user_refs+19@localhost'
166
-        assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+19@localhost>'  # nopep8
165
+        assert headers['References'][0] == 'test_user_refs+22@localhost'
166
+        assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>'  # nopep8
167
 
167
 
168
 
168
 
169
 class TestNotificationsAsync(MailHogTest):
169
 class TestNotificationsAsync(MailHogTest):
263
         assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>'  # nopep8
263
         assert headers['From'][0] == '"Bob i. via Tracim" <test_user_from+3@localhost>'  # nopep8
264
         assert headers['To'][0] == 'Global manager <admin@admin.admin>'
264
         assert headers['To'][0] == 'Global manager <admin@admin.admin>'
265
         assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)'
265
         assert headers['Subject'][0] == '[TRACIM] [Recipes] file1 (Open)'
266
-        assert headers['References'][0] == 'test_user_refs+19@localhost'
267
-        assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+19@localhost>'  # nopep8
266
+        assert headers['References'][0] == 'test_user_refs+22@localhost'
267
+        assert headers['Reply-to'][0] == '"Bob i. & all members of Recipes" <test_user_reply+22@localhost>'  # nopep8

+ 0 - 0
tracim/views/contents_api/__init__.py 查看文件


+ 157 - 0
tracim/views/contents_api/comment_controller.py 查看文件

1
+# coding=utf-8
2
+import transaction
3
+from pyramid.config import Configurator
4
+try:  # Python 3.5+
5
+    from http import HTTPStatus
6
+except ImportError:
7
+    from http import client as HTTPStatus
8
+
9
+from tracim import TracimRequest
10
+from tracim.extensions import hapic
11
+from tracim.lib.core.content import ContentApi
12
+from tracim.lib.core.workspace import WorkspaceApi
13
+from tracim.views.controllers import Controller
14
+from tracim.views.core_api.schemas import CommentSchema
15
+from tracim.views.core_api.schemas import CommentsPathSchema
16
+from tracim.views.core_api.schemas import SetCommentSchema
17
+from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
18
+from tracim.views.core_api.schemas import NoContentSchema
19
+from tracim.exceptions import WorkspaceNotFound
20
+from tracim.exceptions import InsufficientUserProfile
21
+from tracim.exceptions import NotAuthenticated
22
+from tracim.exceptions import AuthenticationFailed
23
+from tracim.models.contents import ContentTypeLegacy as ContentType
24
+from tracim.models.revision_protection import new_revision
25
+
26
+COMMENT_ENDPOINTS_TAG = 'Comments'
27
+
28
+
29
+class CommentController(Controller):
30
+    pass
31
+
32
+    @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
33
+    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
34
+    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
35
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
36
+    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
37
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
38
+    @hapic.output_body(CommentSchema(many=True),)
39
+    def content_comments(self, context, request: TracimRequest, hapic_data=None):
40
+        """
41
+        Get all comments related to a content in asc order (first is the oldest)
42
+        """
43
+
44
+        # login = hapic_data.body
45
+        app_config = request.registry.settings['CFG']
46
+        api = ContentApi(
47
+            current_user=request.current_user,
48
+            session=request.dbsession,
49
+            config=app_config,
50
+        )
51
+        content = api.get_one(
52
+            hapic_data.path.content_id,
53
+            content_type=ContentType.Any
54
+        )
55
+        comments = content.get_comments()
56
+        comments.sort(key=lambda comment: comment.created)
57
+        return [api.get_content_in_context(comment)
58
+                for comment in comments
59
+        ]
60
+
61
+    @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
62
+    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
63
+    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
64
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
65
+    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
66
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
67
+    @hapic.input_body(SetCommentSchema())
68
+    @hapic.output_body(CommentSchema(),)
69
+    def add_comment(self, context, request: TracimRequest, hapic_data=None):
70
+        """
71
+        Add new comment
72
+        """
73
+        # login = hapic_data.body
74
+        app_config = request.registry.settings['CFG']
75
+        api = ContentApi(
76
+            current_user=request.current_user,
77
+            session=request.dbsession,
78
+            config=app_config,
79
+        )
80
+        content = api.get_one(
81
+            hapic_data.path.content_id,
82
+            content_type=ContentType.Any
83
+        )
84
+        comment = api.create_comment(
85
+            content.workspace,
86
+            content,
87
+            hapic_data.body.raw_content,
88
+            do_save=True,
89
+        )
90
+        return api.get_content_in_context(comment)
91
+
92
+    @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
93
+    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
94
+    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
95
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
96
+    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
97
+    @hapic.input_path(CommentsPathSchema())
98
+    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
99
+    def delete_comment(self, context, request: TracimRequest, hapic_data=None):
100
+        """
101
+        Delete comment
102
+        """
103
+        app_config = request.registry.settings['CFG']
104
+        api = ContentApi(
105
+            current_user=request.current_user,
106
+            session=request.dbsession,
107
+            config=app_config,
108
+        )
109
+        wapi = WorkspaceApi(
110
+            current_user=request.current_user,
111
+            session=request.dbsession,
112
+            config=app_config,
113
+        )
114
+        workspace = wapi.get_one(hapic_data.path.workspace_id)
115
+        parent = api.get_one(
116
+            hapic_data.path.content_id,
117
+            content_type=ContentType.Any,
118
+            workspace=workspace
119
+        )
120
+        comment = api.get_one(
121
+            hapic_data.path.comment_id,
122
+            content_type=ContentType.Comment,
123
+            workspace=workspace,
124
+            parent=parent,
125
+        )
126
+        with new_revision(
127
+                session=request.dbsession,
128
+                tm=transaction.manager,
129
+                content=comment
130
+        ):
131
+            api.delete(comment)
132
+        return
133
+
134
+    def bind(self, configurator: Configurator):
135
+        # Get comments
136
+        configurator.add_route(
137
+            'content_comments',
138
+            '/workspaces/{workspace_id}/contents/{content_id}/comments',
139
+            request_method='GET'
140
+        )
141
+        configurator.add_view(self.content_comments, route_name='content_comments')
142
+
143
+        # Add comments
144
+        configurator.add_route(
145
+            'add_comment',
146
+            '/workspaces/{workspace_id}/contents/{content_id}/comments',
147
+            request_method='POST'
148
+        )  # nopep8
149
+        configurator.add_view(self.add_comment, route_name='add_comment')
150
+
151
+        # delete comments
152
+        configurator.add_route(
153
+            'delete_comment',
154
+            '/workspaces/{workspace_id}/contents/{content_id}/comments/{comment_id}',  # nopep8
155
+            request_method='DELETE'
156
+        )
157
+        configurator.add_view(self.delete_comment, route_name='delete_comment')

+ 10 - 1
tracim/views/core_api/schemas.py 查看文件

9
 from tracim.models.contents import GlobalStatus
9
 from tracim.models.contents import GlobalStatus
10
 from tracim.models.contents import open_status
10
 from tracim.models.contents import open_status
11
 from tracim.models.context_models import ContentCreation
11
 from tracim.models.context_models import ContentCreation
12
+from tracim.models.context_models import CommentCreation
13
+from tracim.models.context_models import CommentPath
12
 from tracim.models.context_models import MoveParams
14
 from tracim.models.context_models import MoveParams
13
 from tracim.models.context_models import WorkspaceAndContentPath
15
 from tracim.models.context_models import WorkspaceAndContentPath
14
 from tracim.models.context_models import ContentFilter
16
 from tracim.models.context_models import ContentFilter
100
         description='id of a comment related to content content_id',
102
         description='id of a comment related to content content_id',
101
         required=True
103
         required=True
102
     )
104
     )
105
+    @post_load
106
+    def make_path_object(self, data):
107
+        return CommentPath(**data)
103
 
108
 
104
 
109
 
105
 class FilterContentQuerySchema(marshmallow.Schema):
110
 class FilterContentQuerySchema(marshmallow.Schema):
455
         example='<p>This is just an html comment !</p>'
460
         example='<p>This is just an html comment !</p>'
456
     )
461
     )
457
 
462
 
463
+    @post_load
464
+    def create_comment(self, data):
465
+        return CommentCreation(**data)
466
+
458
 
467
 
459
 class SetContentStatusSchema(marshmallow.Schema):
468
 class SetContentStatusSchema(marshmallow.Schema):
460
     status = marshmallow.fields.Str(
469
     status = marshmallow.fields.Str(
462
         validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
471
         validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
463
         description='this slug is found in content_type available statuses',
472
         description='this slug is found in content_type available statuses',
464
         default=open_status
473
         default=open_status
465
-    )
474
+    )