Parcourir la source

add endpoint and tests for folder

Guénaël Muller il y a 5 ans
Parent
révision
89cbcc45fe

+ 3 - 1
backend/tracim_backend/__init__.py Voir le fichier

@@ -1,6 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3
-
4 3
 try:  # Python 3.5+
5 4
     from http import HTTPStatus
6 5
 except ImportError:
@@ -30,6 +29,7 @@ from tracim_backend.views.core_api.user_controller import UserController
30 29
 from tracim_backend.views.core_api.workspace_controller import WorkspaceController
31 30
 from tracim_backend.views.contents_api.comment_controller import CommentController
32 31
 from tracim_backend.views.contents_api.file_controller import FileController
32
+from tracim_backend.views.contents_api.folder_controller import FolderController
33 33
 from tracim_backend.views.errors import ErrorSchema
34 34
 from tracim_backend.exceptions import NotAuthenticated
35 35
 from tracim_backend.exceptions import UserNotActive
@@ -115,6 +115,7 @@ def web(global_config, **local_settings):
115 115
     html_document_controller = HTMLDocumentController()
116 116
     thread_controller = ThreadController()
117 117
     file_controller = FileController()
118
+    folder_controller = FolderController()
118 119
     configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
119 120
     configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
120 121
     configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
@@ -123,6 +124,7 @@ def web(global_config, **local_settings):
123 124
     configurator.include(html_document_controller.bind, route_prefix=BASE_API_V2)  # nopep8
124 125
     configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
125 126
     configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
127
+    configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
126 128
 
127 129
     hapic.add_documentation_view(
128 130
         '/api/v2/doc',

+ 26 - 11
backend/tracim_backend/models/context_models.py Voir le fichier

@@ -284,10 +284,10 @@ class ContentCreation(object):
284 284
     Content creation model
285 285
     """
286 286
     def __init__(
287
-            self,
288
-            label: str,
289
-            content_type: str,
290
-            parent_id: typing.Optional[int] = None,
287
+        self,
288
+        label: str,
289
+        content_type: str,
290
+        parent_id: typing.Optional[int] = None,
291 291
     ) -> None:
292 292
         self.label = label
293 293
         self.content_type = content_type
@@ -299,8 +299,8 @@ class CommentCreation(object):
299 299
     Comment creation model
300 300
     """
301 301
     def __init__(
302
-            self,
303
-            raw_content: str,
302
+        self,
303
+        raw_content: str,
304 304
     ) -> None:
305 305
         self.raw_content = raw_content
306 306
 
@@ -310,8 +310,8 @@ class SetContentStatus(object):
310 310
     Set content status
311 311
     """
312 312
     def __init__(
313
-            self,
314
-            status: str,
313
+        self,
314
+        status: str,
315 315
     ) -> None:
316 316
         self.status = status
317 317
 
@@ -321,12 +321,27 @@ class TextBasedContentUpdate(object):
321 321
     TextBasedContent update model
322 322
     """
323 323
     def __init__(
324
-            self,
325
-            label: str,
326
-            raw_content: str,
324
+        self,
325
+        label: str,
326
+        raw_content: str,
327
+    ) -> None:
328
+        self.label = label
329
+        self.raw_content = raw_content
330
+
331
+
332
+class FolderContentUpdate(object):
333
+    """
334
+    Folder Content update model
335
+    """
336
+    def __init__(
337
+        self,
338
+        label: str,
339
+        raw_content: str,
340
+        sub_content_types: typing.List[str],
327 341
     ) -> None:
328 342
         self.label = label
329 343
         self.raw_content = raw_content
344
+        self.sub_content_types = sub_content_types
330 345
 
331 346
 
332 347
 class TypeUser(Enum):

+ 761 - 0
backend/tracim_backend/tests/functional/test_contents.py Voir le fichier

@@ -24,6 +24,767 @@ from tracim_backend.tests import set_html_document_slug_to_legacy
24 24
 from tracim_backend.fixtures.content import Content as ContentFixtures
25 25
 from tracim_backend.fixtures.users_and_groups import Base as BaseFixture
26 26
 
27
+class TestFolder(FunctionalTest):
28
+    """
29
+    Tests for /api/v2/workspaces/{workspace_id}/folders/{content_id}
30
+    endpoint
31
+    """
32
+
33
+    fixtures = [BaseFixture]
34
+
35
+    def test_api__get_folder__ok_200__nominal_case(self) -> None:
36
+        """
37
+        Get one folder content
38
+        """
39
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
40
+        admin = dbsession.query(models.User) \
41
+            .filter(models.User.email == 'admin@admin.admin') \
42
+            .one()
43
+        workspace_api = WorkspaceApi(
44
+            current_user=admin,
45
+            session=dbsession,
46
+            config=self.app_config
47
+        )
48
+        content_api = ContentApi(
49
+            current_user=admin,
50
+            session=dbsession,
51
+            config=self.app_config
52
+        )
53
+        test_workspace = workspace_api.create_workspace(
54
+            label='test',
55
+            save_now=True,
56
+        )
57
+        folder = content_api.create(
58
+            label='test-folder',
59
+            content_type_slug=CONTENT_TYPES.Folder.slug,
60
+            workspace=test_workspace,
61
+            do_save=True,
62
+            do_notify=False
63
+        )
64
+        transaction.commit()
65
+
66
+        self.testapp.authorization = (
67
+            'Basic',
68
+            (
69
+                'admin@admin.admin',
70
+                'admin@admin.admin'
71
+            )
72
+        )
73
+        res = self.testapp.get(
74
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(
75
+                workspace_id=test_workspace.workspace_id,
76
+                content_id=folder.content_id,
77
+            ),
78
+            status=200
79
+        )
80
+        content = res.json_body
81
+        assert content['content_type'] == 'folder'
82
+        assert content['content_id'] == folder.content_id
83
+        assert content['is_archived'] is False
84
+        assert content['is_deleted'] is False
85
+        assert content['label'] == 'test-folder'
86
+        assert content['parent_id'] is None
87
+        assert content['show_in_ui'] is True
88
+        assert content['slug'] == 'test-folder'
89
+        assert content['status'] == 'open'
90
+        assert content['workspace_id'] == test_workspace.workspace_id
91
+        assert content['current_revision_id'] == folder.revision_id
92
+        # TODO - G.M - 2018-06-173 - check date format
93
+        assert content['created']
94
+        assert content['author']
95
+        assert content['author']['user_id'] == 1
96
+        assert content['author']['avatar_url'] is None
97
+        assert content['author']['public_name'] == 'Global manager'
98
+        # TODO - G.M - 2018-06-173 - check date format
99
+        assert content['modified']
100
+        assert content['last_modifier']['user_id'] == 1
101
+        assert content['last_modifier']['public_name'] == 'Global manager'
102
+        assert content['last_modifier']['avatar_url'] is None
103
+        assert content['raw_content'] == ''
104
+
105
+    def test_api__get_folder__err_400__wrong_content_type(self) -> None:
106
+        """
107
+        Get one folder of a content content 7 is not folder
108
+        """
109
+        self.testapp.authorization = (
110
+            'Basic',
111
+            (
112
+                'admin@admin.admin',
113
+                'admin@admin.admin'
114
+            )
115
+        )
116
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
117
+        admin = dbsession.query(models.User) \
118
+            .filter(models.User.email == 'admin@admin.admin') \
119
+            .one()
120
+        workspace_api = WorkspaceApi(
121
+            current_user=admin,
122
+            session=dbsession,
123
+            config=self.app_config
124
+        )
125
+        content_api = ContentApi(
126
+            current_user=admin,
127
+            session=dbsession,
128
+            config=self.app_config
129
+        )
130
+        test_workspace = workspace_api.create_workspace(
131
+            label='test',
132
+            save_now=True,
133
+        )
134
+        thread = content_api.create(
135
+            label='thread',
136
+            content_type_slug=CONTENT_TYPES.Thread.slug,
137
+            workspace=test_workspace,
138
+            do_save=True,
139
+            do_notify=False
140
+        )
141
+        transaction.commit()
142
+        self.testapp.get(
143
+            '/api/v2/workspaces/2/folders/7',
144
+            status=400
145
+        )
146
+
147
+    def test_api__get_folder__err_400__content_does_not_exist(self) -> None:  # nopep8
148
+        """
149
+        Get one folder content (content 170 does not exist in db)
150
+        """
151
+        self.testapp.authorization = (
152
+            'Basic',
153
+            (
154
+                'admin@admin.admin',
155
+                'admin@admin.admin'
156
+            )
157
+        )
158
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
159
+        admin = dbsession.query(models.User) \
160
+            .filter(models.User.email == 'admin@admin.admin') \
161
+            .one()
162
+        workspace_api = WorkspaceApi(
163
+            current_user=admin,
164
+            session=dbsession,
165
+            config=self.app_config
166
+        )
167
+        content_api = ContentApi(
168
+            current_user=admin,
169
+            session=dbsession,
170
+            config=self.app_config
171
+        )
172
+        test_workspace = workspace_api.create_workspace(
173
+            label='test',
174
+            save_now=True,
175
+        )
176
+        transaction.commit()
177
+        self.testapp.get(
178
+            '/api/v2/workspaces/{workspace_id}/folders/170'.format(workspace_id=test_workspace.workspace_id),  # nopep8
179
+            status=400
180
+        )
181
+
182
+    def test_api__get_folder__err_400__content_not_in_workspace(self) -> None:  # nopep8
183
+        """
184
+        Get one folders of a content (content is in another workspace)
185
+        """
186
+        self.testapp.authorization = (
187
+            'Basic',
188
+            (
189
+                'admin@admin.admin',
190
+                'admin@admin.admin'
191
+            )
192
+        )
193
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
194
+        admin = dbsession.query(models.User) \
195
+            .filter(models.User.email == 'admin@admin.admin') \
196
+            .one()
197
+        workspace_api = WorkspaceApi(
198
+            current_user=admin,
199
+            session=dbsession,
200
+            config=self.app_config
201
+        )
202
+        content_api = ContentApi(
203
+            current_user=admin,
204
+            session=dbsession,
205
+            config=self.app_config
206
+        )
207
+        test_workspace = workspace_api.create_workspace(
208
+            label='test',
209
+            save_now=True,
210
+        )
211
+        folder = content_api.create(
212
+            label='test_folder',
213
+            content_type_slug=CONTENT_TYPES.Folder.slug,
214
+            workspace=test_workspace,
215
+            do_save=True,
216
+            do_notify=False
217
+        )
218
+        test_workspace2 = workspace_api.create_workspace(
219
+            label='test2',
220
+            save_now=True,
221
+        )
222
+        transaction.commit()
223
+        self.testapp.authorization = (
224
+            'Basic',
225
+            (
226
+                'admin@admin.admin',
227
+                'admin@admin.admin'
228
+            )
229
+        )
230
+        self.testapp.get(
231
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(
232
+                workspace_id=test_workspace2.workspace_id,
233
+                content_id=folder.content_id,
234
+            ),
235
+            status=400
236
+        )
237
+
238
+    def test_api__get_folder__err_400__workspace_does_not_exist(self) -> None:  # nopep8
239
+        """
240
+        Get one folder content (Workspace 40 does not exist)
241
+        """
242
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
243
+        admin = dbsession.query(models.User) \
244
+            .filter(models.User.email == 'admin@admin.admin') \
245
+            .one()
246
+        workspace_api = WorkspaceApi(
247
+            current_user=admin,
248
+            session=dbsession,
249
+            config=self.app_config
250
+        )
251
+        content_api = ContentApi(
252
+            current_user=admin,
253
+            session=dbsession,
254
+            config=self.app_config
255
+        )
256
+        test_workspace = workspace_api.create_workspace(
257
+            label='test',
258
+            save_now=True,
259
+        )
260
+        folder = content_api.create(
261
+            label='test_folder',
262
+            content_type_slug=CONTENT_TYPES.Folder.slug,
263
+            workspace=test_workspace,
264
+            do_save=True,
265
+            do_notify=False
266
+        )
267
+        transaction.commit()
268
+        self.testapp.authorization = (
269
+            'Basic',
270
+            (
271
+                'admin@admin.admin',
272
+                'admin@admin.admin'
273
+            )
274
+        )
275
+        self.testapp.get(
276
+            '/api/v2/workspaces/40/folders/{content_id}'.format(content_id=folder.content_id),  # nopep8
277
+            status=400
278
+        )
279
+
280
+    def test_api__get_folder__err_400__workspace_id_is_not_int(self) -> None:  # nopep8
281
+        """
282
+        Get one folder content, workspace id is not int
283
+        """
284
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
285
+        admin = dbsession.query(models.User) \
286
+            .filter(models.User.email == 'admin@admin.admin') \
287
+            .one()
288
+        workspace_api = WorkspaceApi(
289
+            current_user=admin,
290
+            session=dbsession,
291
+            config=self.app_config
292
+        )
293
+        content_api = ContentApi(
294
+            current_user=admin,
295
+            session=dbsession,
296
+            config=self.app_config
297
+        )
298
+        test_workspace = workspace_api.create_workspace(
299
+            label='test',
300
+            save_now=True,
301
+        )
302
+        folder = content_api.create(
303
+            label='test_folder',
304
+            content_type_slug=CONTENT_TYPES.Folder.slug,
305
+            workspace=test_workspace,
306
+            do_save=True,
307
+            do_notify=False
308
+        )
309
+        transaction.commit()
310
+        self.testapp.authorization = (
311
+            'Basic',
312
+            (
313
+                'admin@admin.admin',
314
+                'admin@admin.admin'
315
+            )
316
+        )
317
+        self.testapp.get(
318
+            '/api/v2/workspaces/coucou/folders/{content_id}'.format(content_id=folder.content_id),  # nopep8
319
+            status=400
320
+        )
321
+
322
+    def test_api__get_folder__err_400__content_id_is_not_int(self) -> None:  # nopep8
323
+        """
324
+        Get one folder content, content_id is not int
325
+        """
326
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
327
+        admin = dbsession.query(models.User) \
328
+            .filter(models.User.email == 'admin@admin.admin') \
329
+            .one()
330
+        workspace_api = WorkspaceApi(
331
+            current_user=admin,
332
+            session=dbsession,
333
+            config=self.app_config
334
+        )
335
+        content_api = ContentApi(
336
+            current_user=admin,
337
+            session=dbsession,
338
+            config=self.app_config
339
+        )
340
+        test_workspace = workspace_api.create_workspace(
341
+            label='test',
342
+            save_now=True,
343
+        )
344
+        folder = content_api.create(
345
+            label='test_folder',
346
+            content_type_slug=CONTENT_TYPES.Folder.slug,
347
+            workspace=test_workspace,
348
+            do_save=True,
349
+            do_notify=False
350
+        )
351
+        transaction.commit()
352
+
353
+        self.testapp.authorization = (
354
+            'Basic',
355
+            (
356
+                'admin@admin.admin',
357
+                'admin@admin.admin'
358
+            )
359
+        )
360
+        self.testapp.get(
361
+            '/api/v2/workspaces/{workspace_id}/folders/coucou'.format(workspace_id=test_workspace.workspace_id),  # nopep8
362
+            status=400
363
+        )
364
+
365
+    def test_api__update_folder__err_400__empty_label(self) -> None:  # nopep8
366
+        """
367
+        Update(put) one folder content
368
+        """
369
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
370
+        admin = dbsession.query(models.User) \
371
+            .filter(models.User.email == 'admin@admin.admin') \
372
+            .one()
373
+        workspace_api = WorkspaceApi(
374
+            current_user=admin,
375
+            session=dbsession,
376
+            config=self.app_config
377
+        )
378
+        content_api = ContentApi(
379
+            current_user=admin,
380
+            session=dbsession,
381
+            config=self.app_config
382
+        )
383
+        test_workspace = workspace_api.create_workspace(
384
+            label='test',
385
+            save_now=True,
386
+        )
387
+        folder = content_api.create(
388
+            label='test_folder',
389
+            content_type_slug=CONTENT_TYPES.Folder.slug,
390
+            workspace=test_workspace,
391
+            do_save=True,
392
+            do_notify=False
393
+        )
394
+        transaction.commit()
395
+        self.testapp.authorization = (
396
+            'Basic',
397
+            (
398
+                'admin@admin.admin',
399
+                'admin@admin.admin'
400
+            )
401
+        )
402
+        params = {
403
+            'label': '',
404
+            'raw_content': '<p> Le nouveau contenu </p>',
405
+            'sub_content_types': [CONTENT_TYPES.Folder.slug]
406
+        }
407
+        self.testapp.put_json(
408
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(
409
+                workspace_id=test_workspace.workspace_id,
410
+                content_id=folder.content_id,
411
+            ),
412
+            params=params,
413
+            status=400
414
+        )
415
+
416
+    def test_api__update_folder__ok_200__nominal_case(self) -> None:
417
+        """
418
+        Update(put) one html document of a content
419
+        """
420
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
421
+        admin = dbsession.query(models.User) \
422
+            .filter(models.User.email == 'admin@admin.admin') \
423
+            .one()
424
+        workspace_api = WorkspaceApi(
425
+            current_user=admin,
426
+            session=dbsession,
427
+            config=self.app_config
428
+        )
429
+        content_api = ContentApi(
430
+            current_user=admin,
431
+            session=dbsession,
432
+            config=self.app_config
433
+        )
434
+        test_workspace = workspace_api.create_workspace(
435
+            label='test',
436
+            save_now=True,
437
+        )
438
+        folder = content_api.create(
439
+            label='test_folder',
440
+            content_type_slug=CONTENT_TYPES.Folder.slug,
441
+            workspace=test_workspace,
442
+            do_save=True,
443
+            do_notify=False
444
+        )
445
+        transaction.commit()
446
+        self.testapp.authorization = (
447
+            'Basic',
448
+            (
449
+                'admin@admin.admin',
450
+                'admin@admin.admin'
451
+            )
452
+        )
453
+        params = {
454
+            'label': 'My New label',
455
+            'raw_content': '<p> Le nouveau contenu </p>',
456
+            'sub_content_types': [CONTENT_TYPES.Folder.slug]
457
+        }
458
+        res = self.testapp.put_json(
459
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(
460
+                workspace_id=test_workspace.workspace_id,
461
+                content_id=folder.content_id,
462
+            ),
463
+            params=params,
464
+            status=200
465
+        )
466
+        content = res.json_body
467
+        assert content['content_type'] == 'folder'
468
+        assert content['content_id'] == folder.content_id
469
+        assert content['is_archived'] is False
470
+        assert content['is_deleted'] is False
471
+        assert content['label'] == 'My New label'
472
+        assert content['parent_id'] is None
473
+        assert content['show_in_ui'] is True
474
+        assert content['slug'] == 'my-new-label'
475
+        assert content['status'] == 'open'
476
+        assert content['workspace_id'] == test_workspace.workspace_id
477
+        assert content['current_revision_id']
478
+        # TODO - G.M - 2018-06-173 - check date format
479
+        assert content['created']
480
+        assert content['author']
481
+        assert content['author']['user_id'] == 1
482
+        assert content['author']['avatar_url'] is None
483
+        assert content['author']['public_name'] == 'Global manager'
484
+        # TODO - G.M - 2018-06-173 - check date format
485
+        assert content['modified']
486
+        assert content['last_modifier'] == content['author']
487
+        assert content['raw_content'] == '<p> Le nouveau contenu </p>'
488
+        assert content['sub_content_types'] == [CONTENT_TYPES.Folder.slug]
489
+
490
+    def test_api__get_folder_revisions__ok_200__nominal_case(
491
+            self
492
+    ) -> None:
493
+        """
494
+        Get one html document of a content
495
+        """
496
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
497
+        admin = dbsession.query(models.User) \
498
+            .filter(models.User.email == 'admin@admin.admin') \
499
+            .one()
500
+        workspace_api = WorkspaceApi(
501
+            current_user=admin,
502
+            session=dbsession,
503
+            config=self.app_config
504
+        )
505
+        content_api = ContentApi(
506
+            current_user=admin,
507
+            session=dbsession,
508
+            config=self.app_config
509
+        )
510
+        test_workspace = workspace_api.create_workspace(
511
+            label='test',
512
+            save_now=True,
513
+        )
514
+        folder = content_api.create(
515
+            label='test-folder',
516
+            content_type_slug=CONTENT_TYPES.Folder.slug,
517
+            workspace=test_workspace,
518
+            do_save=True,
519
+            do_notify=False
520
+        )
521
+        with new_revision(
522
+           session=dbsession,
523
+           tm=transaction.manager,
524
+           content=folder,
525
+        ):
526
+            content_api.update_content(
527
+                folder,
528
+                new_label='test-folder-updated',
529
+                new_content='Just a test'
530
+            )
531
+        content_api.save(folder)
532
+        with new_revision(
533
+           session=dbsession,
534
+           tm=transaction.manager,
535
+           content=folder,
536
+        ):
537
+            content_api.archive(
538
+                folder,
539
+            )
540
+        content_api.save(folder)
541
+        with new_revision(
542
+           session=dbsession,
543
+           tm=transaction.manager,
544
+           content=folder,
545
+        ):
546
+            content_api.unarchive(
547
+                folder,
548
+            )
549
+        content_api.save(folder)
550
+        transaction.commit()
551
+        self.testapp.authorization = (
552
+            'Basic',
553
+            (
554
+                'admin@admin.admin',
555
+                'admin@admin.admin'
556
+            )
557
+        )
558
+        res = self.testapp.get(
559
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}/revisions'.format(  # nopep8
560
+                workspace_id=test_workspace.workspace_id,
561
+                content_id=folder.content_id,
562
+            ),
563
+            status=200
564
+        )
565
+        revisions = res.json_body
566
+        assert len(revisions) == 4
567
+        revision = revisions[0]
568
+        assert revision['content_type'] == 'folder'
569
+        assert revision['content_id'] == folder.content_id
570
+        assert revision['is_archived'] is False
571
+        assert revision['is_deleted'] is False
572
+        assert revision['label'] == 'test-folder'
573
+        assert revision['parent_id'] is None
574
+        assert revision['show_in_ui'] is True
575
+        assert revision['slug'] == 'test-folder'
576
+        assert revision['status'] == 'open'
577
+        assert revision['workspace_id'] == test_workspace.workspace_id
578
+        assert revision['revision_id']
579
+        assert revision['revision_type'] == 'creation'
580
+        assert revision['sub_content_types']
581
+        # TODO - G.M - 2018-06-173 - Test with real comments
582
+        assert revision['comment_ids'] == []
583
+        # TODO - G.M - 2018-06-173 - check date format
584
+        assert revision['created']
585
+        assert revision['author']
586
+        assert revision['author']['user_id'] == 1
587
+        assert revision['author']['avatar_url'] is None
588
+        assert revision['author']['public_name'] == 'Global manager'
589
+
590
+        revision = revisions[1]
591
+        assert revision['content_type'] == 'folder'
592
+        assert revision['content_id'] == folder.content_id
593
+        assert revision['is_archived'] is False
594
+        assert revision['is_deleted'] is False
595
+        assert revision['label'] == 'test-folder-updated'
596
+        assert revision['parent_id'] is None
597
+        assert revision['show_in_ui'] is True
598
+        assert revision['slug'] == 'test-folder-updated'
599
+        assert revision['status'] == 'open'
600
+        assert revision['workspace_id'] == test_workspace.workspace_id
601
+        assert revision['revision_id']
602
+        assert revision['revision_type'] == 'edition'
603
+        assert revision['sub_content_types']
604
+        # TODO - G.M - 2018-06-173 - Test with real comments
605
+        assert revision['comment_ids'] == []
606
+        # TODO - G.M - 2018-06-173 - check date format
607
+        assert revision['created']
608
+        assert revision['author']
609
+        assert revision['author']['user_id'] == 1
610
+        assert revision['author']['avatar_url'] is None
611
+        assert revision['author']['public_name'] == 'Global manager'
612
+
613
+        revision = revisions[2]
614
+        assert revision['content_type'] == 'folder'
615
+        assert revision['content_id'] == folder.content_id
616
+        assert revision['is_archived'] is True
617
+        assert revision['is_deleted'] is False
618
+        assert revision['label'] != 'test-folder-updated'
619
+        assert revision['label'].startswith('test-folder-updated')
620
+        assert revision['parent_id'] is None
621
+        assert revision['show_in_ui'] is True
622
+        assert revision['slug'] != 'test-folder-updated'
623
+        assert revision['slug'].startswith('test-folder-updated')
624
+        assert revision['status'] == 'open'
625
+        assert revision['workspace_id'] == test_workspace.workspace_id
626
+        assert revision['revision_id']
627
+        assert revision['revision_type'] == 'archiving'
628
+        assert revision['sub_content_types']
629
+        # TODO - G.M - 2018-06-173 - Test with real comments
630
+        assert revision['comment_ids'] == []
631
+        # TODO - G.M - 2018-06-173 - check date format
632
+        assert revision['created']
633
+        assert revision['author']
634
+        assert revision['author']['user_id'] == 1
635
+        assert revision['author']['avatar_url'] is None
636
+        assert revision['author']['public_name'] == 'Global manager'
637
+
638
+        revision = revisions[3]
639
+        assert revision['content_type'] == 'folder'
640
+        assert revision['content_id'] == folder.content_id
641
+        assert revision['is_archived'] is False
642
+        assert revision['is_deleted'] is False
643
+        assert revision['label'].startswith('test-folder-updated')
644
+        assert revision['parent_id'] is None
645
+        assert revision['show_in_ui'] is True
646
+        assert revision['slug'].startswith('test-folder-updated')
647
+        assert revision['status'] == 'open'
648
+        assert revision['workspace_id'] == test_workspace.workspace_id
649
+        assert revision['revision_id']
650
+        assert revision['revision_type'] == 'unarchiving'
651
+        assert revision['sub_content_types']
652
+        # TODO - G.M - 2018-06-173 - Test with real comments
653
+        assert revision['comment_ids'] == []
654
+        # TODO - G.M - 2018-06-173 - check date format
655
+        assert revision['created']
656
+        assert revision['author']
657
+        assert revision['author']['user_id'] == 1
658
+        assert revision['author']['avatar_url'] is None
659
+        assert revision['author']['public_name'] == 'Global manager'
660
+
661
+    def test_api__set_folder_status__ok_200__nominal_case(self) -> None:
662
+        """
663
+        Get one folder content
664
+        """
665
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
666
+        admin = dbsession.query(models.User) \
667
+            .filter(models.User.email == 'admin@admin.admin') \
668
+            .one()
669
+        workspace_api = WorkspaceApi(
670
+            current_user=admin,
671
+            session=dbsession,
672
+            config=self.app_config
673
+        )
674
+        content_api = ContentApi(
675
+            current_user=admin,
676
+            session=dbsession,
677
+            config=self.app_config
678
+        )
679
+        test_workspace = workspace_api.create_workspace(
680
+            label='test',
681
+            save_now=True,
682
+        )
683
+        folder = content_api.create(
684
+            label='test_folder',
685
+            content_type_slug=CONTENT_TYPES.Folder.slug,
686
+            workspace=test_workspace,
687
+            do_save=True,
688
+            do_notify=False
689
+        )
690
+        transaction.commit()
691
+        self.testapp.authorization = (
692
+            'Basic',
693
+            (
694
+                'admin@admin.admin',
695
+                'admin@admin.admin'
696
+            )
697
+        )
698
+        params = {
699
+            'status': 'closed-deprecated',
700
+        }
701
+
702
+        # before
703
+        res = self.testapp.get(
704
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(  # nopep8
705
+                # nopep8
706
+                workspace_id=test_workspace.workspace_id,
707
+                content_id=folder.content_id,
708
+            ),
709
+            status=200
710
+        )
711
+        content = res.json_body
712
+        assert content['content_type'] == 'folder'
713
+        assert content['content_id'] == folder.content_id
714
+        assert content['status'] == 'open'
715
+
716
+        # set status
717
+        self.testapp.put_json(
718
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}/status'.format(  # nopep8
719
+                workspace_id=test_workspace.workspace_id,
720
+                content_id=folder.content_id,
721
+            ),
722
+            params=params,
723
+            status=204
724
+        )
725
+
726
+        # after
727
+        res = self.testapp.get(
728
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}'.format(
729
+                workspace_id=test_workspace.workspace_id,
730
+                content_id=folder.content_id,
731
+            ),
732
+            status=200
733
+        )
734
+        content = res.json_body
735
+        assert content['content_type'] == 'folder'
736
+        assert content['content_id'] == folder.content_id
737
+        assert content['status'] == 'closed-deprecated'
738
+
739
+    def test_api__set_folder_status__err_400__wrong_status(self) -> None:
740
+        """
741
+        Get one folder content
742
+        """
743
+        self.testapp.authorization = (
744
+            'Basic',
745
+            (
746
+                'admin@admin.admin',
747
+                'admin@admin.admin'
748
+            )
749
+        )
750
+        params = {
751
+            'status': 'unexistant-status',
752
+        }
753
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
754
+        admin = dbsession.query(models.User) \
755
+            .filter(models.User.email == 'admin@admin.admin') \
756
+            .one()
757
+        workspace_api = WorkspaceApi(
758
+            current_user=admin,
759
+            session=dbsession,
760
+            config=self.app_config
761
+        )
762
+        content_api = ContentApi(
763
+            current_user=admin,
764
+            session=dbsession,
765
+            config=self.app_config
766
+        )
767
+        test_workspace = workspace_api.create_workspace(
768
+            label='test',
769
+            save_now=True,
770
+        )
771
+        folder = content_api.create(
772
+            label='test_folder',
773
+            content_type_slug=CONTENT_TYPES.Folder.slug,
774
+            workspace=test_workspace,
775
+            do_save=True,
776
+            do_notify=False
777
+        )
778
+        transaction.commit()
779
+        self.testapp.put_json(
780
+            '/api/v2/workspaces/{workspace_id}/folders/{content_id}/status'.format(  # nopep8
781
+                workspace_id=test_workspace.workspace_id,
782
+                content_id=folder.content_id,
783
+            ),
784
+            params=params,
785
+            status=400
786
+        )
787
+
27 788
 
28 789
 class TestHtmlDocuments(FunctionalTest):
29 790
     """

+ 190 - 0
backend/tracim_backend/views/contents_api/folder_controller.py Voir le fichier

@@ -0,0 +1,190 @@
1
+# coding=utf-8
2
+import typing
3
+
4
+import transaction
5
+from pyramid.config import Configurator
6
+from tracim_backend.models.data import UserRoleInWorkspace
7
+
8
+try:  # Python 3.5+
9
+    from http import HTTPStatus
10
+except ImportError:
11
+    from http import client as HTTPStatus
12
+
13
+from tracim_backend import TracimRequest
14
+from tracim_backend.extensions import hapic
15
+from tracim_backend.lib.core.content import ContentApi
16
+from tracim_backend.views.controllers import Controller
17
+from tracim_backend.views.core_api.schemas import TextBasedContentSchema
18
+from tracim_backend.views.core_api.schemas import FolderContentModifySchema
19
+from tracim_backend.views.core_api.schemas import TextBasedRevisionSchema
20
+from tracim_backend.views.core_api.schemas import SetContentStatusSchema
21
+from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema  # nopep8
22
+from tracim_backend.views.core_api.schemas import NoContentSchema
23
+from tracim_backend.lib.utils.authorization import require_content_types
24
+from tracim_backend.lib.utils.authorization import require_workspace_role
25
+from tracim_backend.exceptions import EmptyLabelNotAllowed
26
+from tracim_backend.models.context_models import ContentInContext
27
+from tracim_backend.models.context_models import RevisionInContext
28
+from tracim_backend.models.contents import CONTENT_TYPES
29
+from tracim_backend.models.contents import folder_type
30
+from tracim_backend.models.revision_protection import new_revision
31
+
32
+SWAGGER_TAG__Folders_ENDPOINTS = 'Folders'
33
+
34
+
35
+class FolderController(Controller):
36
+
37
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
38
+    @require_workspace_role(UserRoleInWorkspace.READER)
39
+    @require_content_types([folder_type])
40
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
+    @hapic.output_body(TextBasedContentSchema())
42
+    def get_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
43
+        """
44
+        Get folder info
45
+        """
46
+        app_config = request.registry.settings['CFG']
47
+        api = ContentApi(
48
+            current_user=request.current_user,
49
+            session=request.dbsession,
50
+            config=app_config,
51
+        )
52
+        content = api.get_one(
53
+            hapic_data.path.content_id,
54
+            content_type=CONTENT_TYPES.Any_SLUG
55
+        )
56
+        return api.get_content_in_context(content)
57
+
58
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
59
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
60
+    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
61
+    @require_content_types([folder_type])
62
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
63
+    @hapic.input_body(FolderContentModifySchema())
64
+    @hapic.output_body(TextBasedContentSchema())
65
+    def update_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
66
+        """
67
+        update folder
68
+        """
69
+        app_config = request.registry.settings['CFG']
70
+        api = ContentApi(
71
+            current_user=request.current_user,
72
+            session=request.dbsession,
73
+            config=app_config,
74
+        )
75
+        content = api.get_one(
76
+            hapic_data.path.content_id,
77
+            content_type=CONTENT_TYPES.Any_SLUG
78
+        )
79
+        with new_revision(
80
+                session=request.dbsession,
81
+                tm=transaction.manager,
82
+                content=content
83
+        ):
84
+            api.update_content(
85
+                item=content,
86
+                new_label=hapic_data.body.label,
87
+                new_content=hapic_data.body.raw_content,
88
+
89
+            )
90
+            api.set_allowed_content(
91
+                content=content,
92
+                allowed_content_type_slug_list=hapic_data.body.sub_content_types  # nopep8
93
+            )
94
+            api.save(content)
95
+        return api.get_content_in_context(content)
96
+
97
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
98
+    @require_workspace_role(UserRoleInWorkspace.READER)
99
+    @require_content_types([folder_type])
100
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
101
+    @hapic.output_body(TextBasedRevisionSchema(many=True))
102
+    def get_folder_revisions(
103
+            self,
104
+            context,
105
+            request: TracimRequest,
106
+            hapic_data=None
107
+    ) -> typing.List[RevisionInContext]:
108
+        """
109
+        get folder revisions
110
+        """
111
+        app_config = request.registry.settings['CFG']
112
+        api = ContentApi(
113
+            current_user=request.current_user,
114
+            session=request.dbsession,
115
+            config=app_config,
116
+        )
117
+        content = api.get_one(
118
+            hapic_data.path.content_id,
119
+            content_type=CONTENT_TYPES.Any_SLUG
120
+        )
121
+        revisions = content.revisions
122
+        return [
123
+            api.get_revision_in_context(revision)
124
+            for revision in revisions
125
+        ]
126
+
127
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
128
+    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
129
+    @require_content_types([folder_type])
130
+    @hapic.input_path(WorkspaceAndContentIdPathSchema())
131
+    @hapic.input_body(SetContentStatusSchema())
132
+    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
133
+    def set_folder_status(self, context, request: TracimRequest, hapic_data=None) -> None:  # nopep8
134
+        """
135
+        set folder status
136
+        """
137
+        app_config = request.registry.settings['CFG']
138
+        api = ContentApi(
139
+            current_user=request.current_user,
140
+            session=request.dbsession,
141
+            config=app_config,
142
+        )
143
+        content = api.get_one(
144
+            hapic_data.path.content_id,
145
+            content_type=CONTENT_TYPES.Any_SLUG
146
+        )
147
+        with new_revision(
148
+                session=request.dbsession,
149
+                tm=transaction.manager,
150
+                content=content
151
+        ):
152
+            api.set_status(
153
+                content,
154
+                hapic_data.body.status,
155
+            )
156
+            api.save(content)
157
+        return
158
+
159
+    def bind(self, configurator: Configurator) -> None:
160
+        # Get folder
161
+        configurator.add_route(
162
+            'folder',
163
+            '/workspaces/{workspace_id}/folders/{content_id}',
164
+            request_method='GET'
165
+        )
166
+        configurator.add_view(self.get_folder, route_name='folder')  # nopep8
167
+
168
+        # update folder
169
+        configurator.add_route(
170
+            'update_folder',
171
+            '/workspaces/{workspace_id}/folders/{content_id}',
172
+            request_method='PUT'
173
+        )  # nopep8
174
+        configurator.add_view(self.update_folder, route_name='update_folder')  # nopep8
175
+
176
+        # get folder revisions
177
+        configurator.add_route(
178
+            'folder_revisions',
179
+            '/workspaces/{workspace_id}/folders/{content_id}/revisions',  # nopep8
180
+            request_method='GET'
181
+        )
182
+        configurator.add_view(self.get_folder_revisions, route_name='folder_revisions')  # nopep8
183
+
184
+        # get folder revisions
185
+        configurator.add_route(
186
+            'set_folder_status',
187
+            '/workspaces/{workspace_id}/folders/{content_id}/status',  # nopep8
188
+            request_method='PUT'
189
+        )
190
+        configurator.add_view(self.set_folder_status, route_name='set_folder_status')  # nopep8

+ 17 - 0
backend/tracim_backend/views/core_api/schemas.py Voir le fichier

@@ -11,6 +11,7 @@ from tracim_backend.models.contents import CONTENT_STATUS
11 11
 from tracim_backend.models.contents import CONTENT_TYPES
12 12
 from tracim_backend.models.contents import open_status
13 13
 from tracim_backend.models.context_models import ActiveContentFilter
14
+from tracim_backend.models.context_models import FolderContentUpdate
14 15
 from tracim_backend.models.context_models import ContentIdsQuery
15 16
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
16 17
 from tracim_backend.models.context_models import ContentCreation
@@ -832,6 +833,22 @@ class TextBasedContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbs
832 833
         return TextBasedContentUpdate(**data)
833 834
 
834 835
 
836
+class FolderContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbstractSchema):  # nopep
837
+    sub_content_types = marshmallow.fields.List(
838
+        marshmallow.fields.String(
839
+            example='html-document',
840
+            validate=OneOf(CONTENT_TYPES.extended_endpoint_allowed_types_slug())
841
+        ),
842
+        description='list of content types allowed as sub contents. '
843
+                    'This field is required for folder contents, '
844
+                    'set it to empty list in other cases'
845
+    )
846
+
847
+    @post_load
848
+    def folder_content_update(self, data):
849
+        return FolderContentUpdate(**data)
850
+
851
+
835 852
 class FileContentModifySchema(TextBasedContentModifySchema):
836 853
     pass
837 854