Browse Source

refactor contentType and contentStatus legacy code + content_type endpoint

Guénaël Muller 6 years ago
parent
commit
0b35d39def

+ 9 - 1
tracim/exceptions.py View File

@@ -94,4 +94,12 @@ class WrongUserPassword(TracimException):
94 94
 
95 95
 
96 96
 class UserNotExist(TracimException):
97
-    pass
97
+    pass
98
+
99
+
100
+class ContentStatusNotExist(TracimError):
101
+    pass
102
+
103
+
104
+class ContentTypeNotExist(TracimError):
105
+    pass

+ 237 - 0
tracim/models/contents.py View File

@@ -0,0 +1,237 @@
1
+# -*- coding: utf-8 -*-
2
+import typing
3
+from enum import Enum
4
+
5
+from tracim.exceptions import ContentStatusNotExist, ContentTypeNotExist
6
+from tracim.models.applications import pagehtml, file, thread, pagemarkdownplus
7
+
8
+
9
+####
10
+# Content Status
11
+
12
+
13
+class GlobalStatus(Enum):
14
+    OPEN = 'open'
15
+    CLOSED = 'closed'
16
+
17
+
18
+class NewContentStatus(object):
19
+    """
20
+    Future ContentStatus object class
21
+    """
22
+    def __init__(
23
+            self,
24
+            slug: str,
25
+            global_status: str,
26
+            label: str,
27
+            icon: str,
28
+            hexcolor: str,
29
+    ):
30
+        self.slug = slug
31
+        self.global_status = global_status
32
+        self.label = label
33
+        self.icon = icon
34
+        self.hexcolor = hexcolor
35
+
36
+
37
+open_status = NewContentStatus(
38
+    slug='open',
39
+    global_status=GlobalStatus.OPEN.value,
40
+    label='Open',
41
+    icon='fa-square-o',
42
+    hexcolor='#000FF',
43
+)
44
+
45
+closed_validated_status = NewContentStatus(
46
+    slug='closed-validated',
47
+    global_status=GlobalStatus.CLOSED.value,
48
+    label='Validated',
49
+    icon='fa-check-square-o',
50
+    hexcolor='#000FF',
51
+)
52
+
53
+closed_unvalidated_status = NewContentStatus(
54
+    slug='closed-unvalidated',
55
+    global_status=GlobalStatus.CLOSED.value,
56
+    label='Cancelled',
57
+    icon='fa-close',
58
+    hexcolor='#000FF',
59
+)
60
+
61
+closed_deprecated_status = NewContentStatus(
62
+    slug='closed-deprecated',
63
+    global_status=GlobalStatus.CLOSED.value,
64
+    label='Deprecated',
65
+    icon='fa-warning',
66
+    hexcolor='#000FF',
67
+)
68
+
69
+
70
+CONTENT_DEFAULT_STATUS = [
71
+    open_status,
72
+    closed_validated_status,
73
+    closed_unvalidated_status,
74
+    closed_deprecated_status,
75
+]
76
+
77
+
78
+class ContentStatusLegacy(NewContentStatus):
79
+    """
80
+    Temporary remplacement object for Legacy ContentStatus Object
81
+    """
82
+    OPEN = open_status.slug
83
+    CLOSED_VALIDATED = closed_validated_status.slug
84
+    CLOSED_UNVALIDATED = closed_unvalidated_status.slug
85
+    CLOSED_DEPRECATED = closed_deprecated_status.slug
86
+
87
+    def __init__(self, slug: str):
88
+        for status in CONTENT_DEFAULT_STATUS:
89
+            if slug == status.slug:
90
+                super(ContentStatusLegacy).__init__(
91
+                    slug=status.slug,
92
+                    global_status=status.global_status,
93
+                    label=status.label,
94
+                    icon=status.icon,
95
+                    hexcolor=status.hexcolor,
96
+                )
97
+                return
98
+        raise ContentStatusNotExist()
99
+
100
+    @classmethod
101
+    def all(cls, type='') -> ['NewContentStatus']:
102
+        return CONTENT_DEFAULT_STATUS
103
+
104
+    @classmethod
105
+    def allowed_values(cls):
106
+        return [status.slug for status in CONTENT_DEFAULT_STATUS]
107
+
108
+
109
+####
110
+# ContentType
111
+
112
+
113
+class NewContentType(object):
114
+    """
115
+    Future ContentType object class
116
+    """
117
+    def __init__(
118
+            self,
119
+            slug: str,
120
+            icon: str,
121
+            hexcolor: str,
122
+            label: str,
123
+            creation_label: str,
124
+            available_statuses: typing.List[NewContentStatus],
125
+
126
+    ):
127
+        self.slug = slug
128
+        self.icon = icon
129
+        self.hexcolor = hexcolor
130
+        self.label = label
131
+        self.creation_label = creation_label
132
+        self.available_statuses = available_statuses
133
+
134
+
135
+thread_type = NewContentType(
136
+    slug='thread',
137
+    icon=thread.icon,
138
+    hexcolor=thread.hexcolor,
139
+    label='Thread',
140
+    creation_label='Discuss about a topic',
141
+    available_statuses=CONTENT_DEFAULT_STATUS,
142
+)
143
+
144
+file_type = NewContentType(
145
+    slug='file',
146
+    icon=file.icon,
147
+    hexcolor=file.hexcolor,
148
+    label='File',
149
+    creation_label='Upload a file',
150
+    available_statuses=CONTENT_DEFAULT_STATUS,
151
+)
152
+
153
+pagemarkdownplus_type = NewContentType(
154
+    slug='markdownpage',
155
+    icon=pagemarkdownplus.icon,
156
+    hexcolor=pagemarkdownplus.hexcolor,
157
+    label='Rich Markdown File',
158
+    creation_label='Create a Markdown document',
159
+    available_statuses=CONTENT_DEFAULT_STATUS,
160
+)
161
+
162
+pagehtml_type = NewContentType(
163
+    slug='page',
164
+    icon=pagehtml.icon,
165
+    hexcolor=pagehtml.hexcolor,
166
+    label='Text Document',
167
+    creation_label='Write a document',
168
+    available_statuses=CONTENT_DEFAULT_STATUS,
169
+)
170
+
171
+
172
+CONTENT_DEFAULT_TYPE = [
173
+    thread_type,
174
+    file_type,
175
+    pagemarkdownplus_type,
176
+    pagehtml_type,
177
+]
178
+
179
+
180
+class ContentTypeLegacy(NewContentType):
181
+    """
182
+    Temporary remplacement object for Legacy ContentType Object
183
+    """
184
+
185
+    # special type
186
+    Any = 'any'
187
+    Folder = 'folder'
188
+    Event = 'event'
189
+    Comment = 'comment'
190
+
191
+    File = file_type.slug
192
+    Thread = thread_type.slug
193
+    Page = pagehtml_type.slug
194
+
195
+    def __init__(self, slug: str):
196
+        for content_type in CONTENT_DEFAULT_TYPE:
197
+            if slug == content_type.slug:
198
+                super(ContentTypeLegacy).__init__(
199
+                    slug=content_type.slug,
200
+                    icon=content_type.icon,
201
+                    hexcolor=content_type.hexcolor,
202
+                    label=content_type.label,
203
+                    creation_label=content_type.creation_label,
204
+                    available_statuses=content_type.available_statuses
205
+                )
206
+                return
207
+        raise ContentTypeNotExist()
208
+
209
+    @classmethod
210
+    def all(cls) -> typing.List[str]:
211
+        return cls.allowed_types()
212
+
213
+    @classmethod
214
+    def allowed_types(cls) -> typing.List[str]:
215
+        contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
216
+        contents_types.extend([cls.Folder, cls.Event, cls.Comment])
217
+        return contents_types
218
+
219
+    @classmethod
220
+    def allowed_types_for_folding(cls):
221
+        # This method is used for showing only "main"
222
+        # types in the left-side treeview
223
+        contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
224
+        contents_types.extend([cls.Folder])
225
+        return contents_types
226
+
227
+    # TODO - G.M - 30-05-2018 - This method don't do anything.
228
+    @classmethod
229
+    def sorted(cls, types: ['ContentType']) -> ['ContentType']:
230
+        return types
231
+
232
+    @property
233
+    def id(self):
234
+        return self.slug
235
+
236
+    def toDict(self):
237
+        raise NotImplementedError()

+ 248 - 243
tracim/models/data.py View File

@@ -289,249 +289,254 @@ class ActionDescription(object):
289 289
                 ]
290 290
 
291 291
 
292
-class ContentStatus(object):
293
-    """
294
-    Allowed status are:
295
-    - open
296
-    - closed-validated
297
-    - closed-invalidated
298
-    - closed-deprecated
299
-    """
300
-
301
-    OPEN = 'open'
302
-    CLOSED_VALIDATED = 'closed-validated'
303
-    CLOSED_UNVALIDATED = 'closed-unvalidated'
304
-    CLOSED_DEPRECATED = 'closed-deprecated'
305
-
306
-    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
307
-    # _LABELS = {'open': l_('work in progress'),
308
-    #            'closed-validated': l_('closed — validated'),
309
-    #            'closed-unvalidated': l_('closed — cancelled'),
310
-    #            'closed-deprecated': l_('deprecated')}
311
-    #
312
-    # _LABELS_THREAD = {'open': l_('subject in progress'),
313
-    #                   'closed-validated': l_('subject closed — resolved'),
314
-    #                   'closed-unvalidated': l_('subject closed — cancelled'),
315
-    #                   'closed-deprecated': l_('deprecated')}
316
-    #
317
-    # _LABELS_FILE = {'open': l_('work in progress'),
318
-    #                 'closed-validated': l_('closed — validated'),
319
-    #                 'closed-unvalidated': l_('closed — cancelled'),
320
-    #                 'closed-deprecated': l_('deprecated')}
321
-    #
322
-    # _ICONS = {
323
-    #     'open': 'fa fa-square-o',
324
-    #     'closed-validated': 'fa fa-check-square-o',
325
-    #     'closed-unvalidated': 'fa fa-close',
326
-    #     'closed-deprecated': 'fa fa-warning',
327
-    # }
328
-    #
329
-    # _CSS = {
330
-    #     'open': 'tracim-status-open',
331
-    #     'closed-validated': 'tracim-status-closed-validated',
332
-    #     'closed-unvalidated': 'tracim-status-closed-unvalidated',
333
-    #     'closed-deprecated': 'tracim-status-closed-deprecated',
334
-    # }
335
-
336
-    def __init__(self,
337
-                 id,
338
-                 # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
339
-                 # type=''
340
-    ):
341
-        self.id = id
342
-        # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
343
-        # self.icon = ContentStatus._ICONS[id]
344
-        # self.css = ContentStatus._CSS[id]
345
-        #
346
-        # if type==ContentType.Thread:
347
-        #     self.label = ContentStatus._LABELS_THREAD[id]
348
-        # elif type==ContentType.File:
349
-        #     self.label = ContentStatus._LABELS_FILE[id]
350
-        # else:
351
-        #     self.label = ContentStatus._LABELS[id]
352
-
353
-
354
-    @classmethod
355
-    def all(cls, type='') -> ['ContentStatus']:
356
-        # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
357
-        # all = []
358
-        # all.append(ContentStatus('open', type))
359
-        # all.append(ContentStatus('closed-validated', type))
360
-        # all.append(ContentStatus('closed-unvalidated', type))
361
-        # all.append(ContentStatus('closed-deprecated', type))
362
-        # return all
363
-        status_list = list()
364
-        for elem in cls.allowed_values():
365
-            status_list.append(ContentStatus(elem))
366
-        return status_list
367
-
368
-    @classmethod
369
-    def allowed_values(cls):
370
-        # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
371
-        # return ContentStatus._LABELS.keys()
372
-        return [
373
-            ContentStatus.OPEN,
374
-            ContentStatus.CLOSED_UNVALIDATED,
375
-            ContentStatus.CLOSED_VALIDATED,
376
-            ContentStatus.CLOSED_DEPRECATED
377
-        ]
378
-
379
-
380
-class ContentType(object):
381
-    Any = 'any'
382
-
383
-    Folder = 'folder'
384
-    File = 'file'
385
-    Comment = 'comment'
386
-    Thread = 'thread'
387
-    Page = 'page'
388
-    Event = 'event'
389
-
390
-    # TODO - G.M - 10-04-2018 - [Cleanup] Do we really need this ?
391
-    # _STRING_LIST_SEPARATOR = ','
392
-
393
-    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
394
-    # _ICONS = {  # Deprecated
395
-    #     'dashboard': 'fa-home',
396
-    #     'workspace': 'fa-bank',
397
-    #     'folder': 'fa fa-folder-open-o',
398
-    #     'file': 'fa fa-paperclip',
399
-    #     'page': 'fa fa-file-text-o',
400
-    #     'thread': 'fa fa-comments-o',
401
-    #     'comment': 'fa fa-comment-o',
402
-    #     'event': 'fa fa-calendar-o',
403
-    # }
404
-    #
405
-    # _CSS_ICONS = {
406
-    #     'dashboard': 'fa fa-home',
407
-    #     'workspace': 'fa fa-bank',
408
-    #     'folder': 'fa fa-folder-open-o',
409
-    #     'file': 'fa fa-paperclip',
410
-    #     'page': 'fa fa-file-text-o',
411
-    #     'thread': 'fa fa-comments-o',
412
-    #     'comment': 'fa fa-comment-o',
413
-    #     'event': 'fa fa-calendar-o',
414
-    # }
415
-    #
416
-    # _CSS_COLORS = {
417
-    #     'dashboard': 't-dashboard-color',
418
-    #     'workspace': 't-less-visible',
419
-    #     'folder': 't-folder-color',
420
-    #     'file': 't-file-color',
421
-    #     'page': 't-page-color',
422
-    #     'thread': 't-thread-color',
423
-    #     'comment': 't-thread-color',
424
-    #     'event': 't-event-color',
425
-    # }
426
-
427
-    _ORDER_WEIGHT = {
428
-        'folder': 0,
429
-        'page': 1,
430
-        'thread': 2,
431
-        'file': 3,
432
-        'comment': 4,
433
-        'event': 5,
434
-    }
435
-
436
-    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
437
-    # _LABEL = {
438
-    #     'dashboard': '',
439
-    #     'workspace': l_('workspace'),
440
-    #     'folder': l_('folder'),
441
-    #     'file': l_('file'),
442
-    #     'page': l_('page'),
443
-    #     'thread': l_('thread'),
444
-    #     'comment': l_('comment'),
445
-    #     'event': l_('event'),
446
-    # }
447
-    #
448
-    # _DELETE_LABEL = {
449
-    #     'dashboard': '',
450
-    #     'workspace': l_('Delete this workspace'),
451
-    #     'folder': l_('Delete this folder'),
452
-    #     'file': l_('Delete this file'),
453
-    #     'page': l_('Delete this page'),
454
-    #     'thread': l_('Delete this thread'),
455
-    #     'comment': l_('Delete this comment'),
456
-    #     'event': l_('Delete this event'),
457
-    # }
458
-    #
459
-    # @classmethod
460
-    # def get_icon(cls, type: str):
461
-    #     assert(type in ContentType._ICONS) # DYN_REMOVE
462
-    #     return ContentType._ICONS[type]
463
-
464
-    @classmethod
465
-    def all(cls):
466
-        return cls.allowed_types()
467
-
468
-    @classmethod
469
-    def allowed_types(cls):
470
-        return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page,
471
-                cls.Event]
472
-
473
-    @classmethod
474
-    def allowed_types_for_folding(cls):
475
-        # This method is used for showing only "main"
476
-        # types in the left-side treeview
477
-        return [cls.Folder, cls.File, cls.Thread, cls.Page]
478
-
479
-    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
480
-    # @classmethod
481
-    # def allowed_types_from_str(cls, allowed_types_as_string: str):
482
-    #     allowed_types = []
483
-    #     # HACK - THIS
484
-    #     for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
485
-    #         if item and item in ContentType.allowed_types_for_folding():
486
-    #             allowed_types.append(item)
487
-    #     return allowed_types
488
-    #
489
-    # @classmethod
490
-    # def fill_url(cls, content: 'Content'):
491
-    #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
492
-    #     # Make this code dynamic loading data types
493
-    #
494
-    #     if content.type==ContentType.Folder:
495
-    #         return '/workspaces/{}/folders/{}'.format(content.workspace_id, content.content_id)
496
-    #     elif content.type==ContentType.File:
497
-    #         return '/workspaces/{}/folders/{}/files/{}'.format(content.workspace_id, content.parent_id, content.content_id)
498
-    #     elif content.type==ContentType.Thread:
499
-    #         return '/workspaces/{}/folders/{}/threads/{}'.format(content.workspace_id, content.parent_id, content.content_id)
500
-    #     elif content.type==ContentType.Page:
501
-    #         return '/workspaces/{}/folders/{}/pages/{}'.format(content.workspace_id, content.parent_id, content.content_id)
502
-    #
503
-    # @classmethod
504
-    # def fill_url_for_workspace(cls, workspace: Workspace):
505
-    #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
506
-    #     # Make this code dynamic loading data types
507
-    #     return '/workspaces/{}'.format(workspace.workspace_id)
508
-
509
-    @classmethod
510
-    def sorted(cls, types: ['ContentType']) -> ['ContentType']:
511
-        return sorted(types, key=lambda content_type: content_type.priority)
512
-
513
-    @property
514
-    def type(self):
515
-        return self.id
516
-
517
-    def __init__(self, type):
518
-        self.id = type
519
-        # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
520
-        # self.icon = ContentType._CSS_ICONS[type]
521
-        # self.color = ContentType._CSS_COLORS[type]  # deprecated
522
-        # self.css = ContentType._CSS_COLORS[type]
523
-        # self.label = ContentType._LABEL[type]
524
-        self.priority = ContentType._ORDER_WEIGHT[type]
525
-
526
-    def toDict(self):
527
-        return dict(id=self.type,
528
-                    type=self.type,
529
-                    # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
530
-                    # icon=self.icon,
531
-                    # color=self.color,
532
-                    # label=self.label,
533
-                    priority=self.priority)
534
-
292
+from .contents import ContentStatusLegacy as ContentStatus
293
+from .contents import ContentTypeLegacy as ContentType
294
+# TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
295
+# is lost .
296
+
297
+
298
+# class ContentStatus(object):
299
+#     """
300
+#     Allowed status are:
301
+#     - open
302
+#     - closed-validated
303
+#     - closed-invalidated
304
+#     - closed-deprecated
305
+#     """
306
+#
307
+#     OPEN = 'open'
308
+#     CLOSED_VALIDATED = 'closed-validated'
309
+#     CLOSED_UNVALIDATED = 'closed-unvalidated'
310
+#     CLOSED_DEPRECATED = 'closed-deprecated'
311
+#
312
+#     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
313
+#     # _LABELS = {'open': l_('work in progress'),
314
+#     #            'closed-validated': l_('closed — validated'),
315
+#     #            'closed-unvalidated': l_('closed — cancelled'),
316
+#     #            'closed-deprecated': l_('deprecated')}
317
+#     #
318
+#     # _LABELS_THREAD = {'open': l_('subject in progress'),
319
+#     #                   'closed-validated': l_('subject closed — resolved'),
320
+#     #                   'closed-unvalidated': l_('subject closed — cancelled'),
321
+#     #                   'closed-deprecated': l_('deprecated')}
322
+#     #
323
+#     # _LABELS_FILE = {'open': l_('work in progress'),
324
+#     #                 'closed-validated': l_('closed — validated'),
325
+#     #                 'closed-unvalidated': l_('closed — cancelled'),
326
+#     #                 'closed-deprecated': l_('deprecated')}
327
+#     #
328
+#     # _ICONS = {
329
+#     #     'open': 'fa fa-square-o',
330
+#     #     'closed-validated': 'fa fa-check-square-o',
331
+#     #     'closed-unvalidated': 'fa fa-close',
332
+#     #     'closed-deprecated': 'fa fa-warning',
333
+#     # }
334
+#     #
335
+#     # _CSS = {
336
+#     #     'open': 'tracim-status-open',
337
+#     #     'closed-validated': 'tracim-status-closed-validated',
338
+#     #     'closed-unvalidated': 'tracim-status-closed-unvalidated',
339
+#     #     'closed-deprecated': 'tracim-status-closed-deprecated',
340
+#     # }
341
+#
342
+#     def __init__(self,
343
+#                  id,
344
+#                  # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
345
+#                  # type=''
346
+#     ):
347
+#         self.id = id
348
+#         # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
349
+#         # self.icon = ContentStatus._ICONS[id]
350
+#         # self.css = ContentStatus._CSS[id]
351
+#         #
352
+#         # if type==ContentType.Thread:
353
+#         #     self.label = ContentStatus._LABELS_THREAD[id]
354
+#         # elif type==ContentType.File:
355
+#         #     self.label = ContentStatus._LABELS_FILE[id]
356
+#         # else:
357
+#         #     self.label = ContentStatus._LABELS[id]
358
+#
359
+#
360
+#     @classmethod
361
+#     def all(cls, type='') -> ['ContentStatus']:
362
+#         # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
363
+#         # all = []
364
+#         # all.append(ContentStatus('open', type))
365
+#         # all.append(ContentStatus('closed-validated', type))
366
+#         # all.append(ContentStatus('closed-unvalidated', type))
367
+#         # all.append(ContentStatus('closed-deprecated', type))
368
+#         # return all
369
+#         status_list = list()
370
+#         for elem in cls.allowed_values():
371
+#             status_list.append(ContentStatus(elem))
372
+#         return status_list
373
+#
374
+#     @classmethod
375
+#     def allowed_values(cls):
376
+#         # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
377
+#         # return ContentStatus._LABELS.keys()
378
+#         return [
379
+#             ContentStatus.OPEN,
380
+#             ContentStatus.CLOSED_UNVALIDATED,
381
+#             ContentStatus.CLOSED_VALIDATED,
382
+#             ContentStatus.CLOSED_DEPRECATED
383
+#         ]
384
+
385
+
386
+# class ContentType(object):
387
+#     Any = 'any'
388
+#
389
+#     Folder = 'folder'
390
+#     File = 'file'
391
+#     Comment = 'comment'
392
+#     Thread = 'thread'
393
+#     Page = 'page'
394
+#     Event = 'event'
395
+#
396
+#     # TODO - G.M - 10-04-2018 - [Cleanup] Do we really need this ?
397
+#     # _STRING_LIST_SEPARATOR = ','
398
+#
399
+#     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
400
+#     # _ICONS = {  # Deprecated
401
+#     #     'dashboard': 'fa-home',
402
+#     #     'workspace': 'fa-bank',
403
+#     #     'folder': 'fa fa-folder-open-o',
404
+#     #     'file': 'fa fa-paperclip',
405
+#     #     'page': 'fa fa-file-text-o',
406
+#     #     'thread': 'fa fa-comments-o',
407
+#     #     'comment': 'fa fa-comment-o',
408
+#     #     'event': 'fa fa-calendar-o',
409
+#     # }
410
+#     #
411
+#     # _CSS_ICONS = {
412
+#     #     'dashboard': 'fa fa-home',
413
+#     #     'workspace': 'fa fa-bank',
414
+#     #     'folder': 'fa fa-folder-open-o',
415
+#     #     'file': 'fa fa-paperclip',
416
+#     #     'page': 'fa fa-file-text-o',
417
+#     #     'thread': 'fa fa-comments-o',
418
+#     #     'comment': 'fa fa-comment-o',
419
+#     #     'event': 'fa fa-calendar-o',
420
+#     # }
421
+#     #
422
+#     # _CSS_COLORS = {
423
+#     #     'dashboard': 't-dashboard-color',
424
+#     #     'workspace': 't-less-visible',
425
+#     #     'folder': 't-folder-color',
426
+#     #     'file': 't-file-color',
427
+#     #     'page': 't-page-color',
428
+#     #     'thread': 't-thread-color',
429
+#     #     'comment': 't-thread-color',
430
+#     #     'event': 't-event-color',
431
+#     # }
432
+#
433
+#     _ORDER_WEIGHT = {
434
+#         'folder': 0,
435
+#         'page': 1,
436
+#         'thread': 2,
437
+#         'file': 3,
438
+#         'comment': 4,
439
+#         'event': 5,
440
+#     }
441
+#
442
+#     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
443
+#     # _LABEL = {
444
+#     #     'dashboard': '',
445
+#     #     'workspace': l_('workspace'),
446
+#     #     'folder': l_('folder'),
447
+#     #     'file': l_('file'),
448
+#     #     'page': l_('page'),
449
+#     #     'thread': l_('thread'),
450
+#     #     'comment': l_('comment'),
451
+#     #     'event': l_('event'),
452
+#     # }
453
+#     #
454
+#     # _DELETE_LABEL = {
455
+#     #     'dashboard': '',
456
+#     #     'workspace': l_('Delete this workspace'),
457
+#     #     'folder': l_('Delete this folder'),
458
+#     #     'file': l_('Delete this file'),
459
+#     #     'page': l_('Delete this page'),
460
+#     #     'thread': l_('Delete this thread'),
461
+#     #     'comment': l_('Delete this comment'),
462
+#     #     'event': l_('Delete this event'),
463
+#     # }
464
+#     #
465
+#     # @classmethod
466
+#     # def get_icon(cls, type: str):
467
+#     #     assert(type in ContentType._ICONS) # DYN_REMOVE
468
+#     #     return ContentType._ICONS[type]
469
+#
470
+#     @classmethod
471
+#     def all(cls):
472
+#         return cls.allowed_types()
473
+#
474
+#     @classmethod
475
+#     def allowed_types(cls):
476
+#         return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page,
477
+#                 cls.Event]
478
+#
479
+#     @classmethod
480
+#     def allowed_types_for_folding(cls):
481
+#         # This method is used for showing only "main"
482
+#         # types in the left-side treeview
483
+#         return [cls.Folder, cls.File, cls.Thread, cls.Page]
484
+#
485
+#     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
486
+#     # @classmethod
487
+#     # def allowed_types_from_str(cls, allowed_types_as_string: str):
488
+#     #     allowed_types = []
489
+#     #     # HACK - THIS
490
+#     #     for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
491
+#     #         if item and item in ContentType.allowed_types_for_folding():
492
+#     #             allowed_types.append(item)
493
+#     #     return allowed_types
494
+#     #
495
+#     # @classmethod
496
+#     # def fill_url(cls, content: 'Content'):
497
+#     #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
498
+#     #     # Make this code dynamic loading data types
499
+#     #
500
+#     #     if content.type==ContentType.Folder:
501
+#     #         return '/workspaces/{}/folders/{}'.format(content.workspace_id, content.content_id)
502
+#     #     elif content.type==ContentType.File:
503
+#     #         return '/workspaces/{}/folders/{}/files/{}'.format(content.workspace_id, content.parent_id, content.content_id)
504
+#     #     elif content.type==ContentType.Thread:
505
+#     #         return '/workspaces/{}/folders/{}/threads/{}'.format(content.workspace_id, content.parent_id, content.content_id)
506
+#     #     elif content.type==ContentType.Page:
507
+#     #         return '/workspaces/{}/folders/{}/pages/{}'.format(content.workspace_id, content.parent_id, content.content_id)
508
+#     #
509
+#     # @classmethod
510
+#     # def fill_url_for_workspace(cls, workspace: Workspace):
511
+#     #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
512
+#     #     # Make this code dynamic loading data types
513
+#     #     return '/workspaces/{}'.format(workspace.workspace_id)
514
+#
515
+#     @classmethod
516
+#     def sorted(cls, types: ['ContentType']) -> ['ContentType']:
517
+#         return sorted(types, key=lambda content_type: content_type.priority)
518
+#
519
+#     @property
520
+#     def type(self):
521
+#         return self.id
522
+#
523
+#     def __init__(self, type):
524
+#         self.id = type
525
+#         # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
526
+#         # self.icon = ContentType._CSS_ICONS[type]
527
+#         # self.color = ContentType._CSS_COLORS[type]  # deprecated
528
+#         # self.css = ContentType._CSS_COLORS[type]
529
+#         # self.label = ContentType._LABEL[type]
530
+#         self.priority = ContentType._ORDER_WEIGHT[type]
531
+#
532
+#     def toDict(self):
533
+#         return dict(id=self.type,
534
+#                     type=self.type,
535
+#                     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
536
+#                     # icon=self.icon,
537
+#                     # color=self.color,
538
+#                     # label=self.label,
539
+#                     priority=self.priority)
535 540
 
536 541
 class ContentChecker(object):
537 542
 

+ 32 - 2
tracim/tests/functional/test_system.py View File

@@ -95,13 +95,43 @@ class TestContentsTypesEndpoint(FunctionalTest):
95 95
         )
96 96
         res = self.testapp.get('/api/v2/system/content_types', status=200)
97 97
         res = res.json_body
98
+
98 99
         content_type = res[0]
99
-        assert content_type['slug'] == 'pagehtml'
100
+        assert content_type['slug'] == 'thread'
101
+        assert content_type['icon'] == 'comments-o'
102
+        assert content_type['hexcolor'] == '#ad4cf9'
103
+        assert content_type['label'] == 'Thread'
104
+        assert content_type['creation_label'] == 'Discuss about a topic'
105
+        assert 'available_statuses' in content_type
106
+        assert len(content_type['available_statuses']) == 4
107
+
108
+        content_type = res[1]
109
+        assert content_type['slug'] == 'file'
110
+        assert content_type['icon'] == 'paperclip'
111
+        assert content_type['hexcolor'] == '#FF9900'
112
+        assert content_type['label'] == 'File'
113
+        assert content_type['creation_label'] == 'Upload a file'
114
+        assert 'available_statuses' in content_type
115
+        assert len(content_type['available_statuses']) == 4
116
+
117
+        content_type = res[2]
118
+        assert content_type['slug'] == 'markdownpage'
119
+        assert content_type['icon'] == 'file-code'
120
+        assert content_type['hexcolor'] == '#f12d2d'
121
+        assert content_type['label'] == 'Rich Markdown File'
122
+        assert content_type['creation_label'] == 'Create a Markdown document'
123
+        assert 'available_statuses' in content_type
124
+        assert len(content_type['available_statuses']) == 4
125
+
126
+        content_type = res[3]
127
+        assert content_type['slug'] == 'page'
100 128
         assert content_type['icon'] == 'file-text-o'
101 129
         assert content_type['hexcolor'] == '#3f52e3'
102
-        assert content_type['label'] == 'Text Documents'
130
+        assert content_type['label'] == 'Text Document'
103 131
         assert content_type['creation_label'] == 'Write a document'
104 132
         assert 'available_statuses' in content_type
133
+        assert len(content_type['available_statuses']) == 4
134
+
105 135
         # TODO - G.M - 29-05-2018 - Better check for available_statuses
106 136
 
107 137
     def test_api__get_content_types__err_401__unregistered_user(self):

+ 154 - 2
tracim/views/core_api/schemas.py View File

@@ -4,6 +4,7 @@ from marshmallow import post_load
4 4
 from marshmallow.validate import OneOf
5 5
 
6 6
 from tracim.models.auth import Profile
7
+from tracim.models.contents import CONTENT_DEFAULT_TYPE, GlobalStatus, CONTENT_DEFAULT_STATUS
7 8
 from tracim.models.context_models import LoginCredentials
8 9
 from tracim.models.data import UserRoleInWorkspace
9 10
 
@@ -65,12 +66,60 @@ class UserSchema(marshmallow.Schema):
65 66
         description = 'User account of Tracim'
66 67
 
67 68
 
69
+# Path Schemas
70
+
71
+
68 72
 class UserIdPathSchema(marshmallow.Schema):
69
-    user_id = marshmallow.fields.Int(example=3)
73
+    user_id = marshmallow.fields.Int(example=3, required=True)
70 74
 
71 75
 
72 76
 class WorkspaceIdPathSchema(marshmallow.Schema):
73
-    workspace_id = marshmallow.fields.Int(example=4)
77
+    workspace_id = marshmallow.fields.Int(example=4, required=True)
78
+
79
+
80
+class ContentIdPathSchema(marshmallow.Schema):
81
+    content_id = marshmallow.fields.Int(example=6, required=True)
82
+
83
+
84
+class WorkspaceAndContentIdPathSchema(WorkspaceIdPathSchema, ContentIdPathSchema):
85
+    pass
86
+
87
+
88
+class FilterContentPathSchema(marshmallow.Schema):
89
+    workspace_id = marshmallow.fields.Int(example=4, required=True)
90
+    parent_id = workspace_id = marshmallow.fields.Int(
91
+        example=2,
92
+        default=None,
93
+        description='allow to filter items in a folder.'
94
+                    ' If not set, then return all contents.'
95
+                    ' If set to 0, then return root contents.'
96
+                    ' If set to another value, return all contents'
97
+                    ' directly included in the folder parent_id'
98
+    )
99
+    show_archived = marshmallow.fields.Int(
100
+        example=0,
101
+        default=0,
102
+        description='if set to 1, then show archived contents.'
103
+                    ' Default is 0 - hide archived content'
104
+    )
105
+    show_deleted = marshmallow.fields.Int(
106
+        example=0,
107
+        default=0,
108
+        description='if set to 1, then show deleted contents.'
109
+                    ' Default is 0 - hide deleted content'
110
+    )
111
+    show_active = marshmallow.fields.Int(
112
+        example=1,
113
+        default=1,
114
+        description='f set to 1, then show active contents. '
115
+                    'Default is 1 - show active content.'
116
+                    ' Note: active content are content '
117
+                    'that is neither archived nor deleted. '
118
+                    'The reason for this parameter to exist is for example '
119
+                    'to allow to show only archived documents'
120
+    )
121
+
122
+###
74 123
 
75 124
 
76 125
 class BasicAuthSchema(marshmallow.Schema):
@@ -194,3 +243,106 @@ class ApplicationSchema(marshmallow.Schema):
194 243
 
195 244
     class Meta:
196 245
         description = 'Tracim Application informations'
246
+
247
+
248
+class StatusSchema(marshmallow.Schema):
249
+    slug = marshmallow.fields.String(
250
+        example='open',
251
+        description='the slug represents the type of status. '
252
+                    'Statuses are open, closed-validated, closed-invalidated, closed-deprecated'  # nopep8
253
+    )
254
+    global_status = marshmallow.fields.String(
255
+        example='Open',
256
+        description='global_status: open, closed',
257
+        validate=OneOf([status.value for status in GlobalStatus]),
258
+    )
259
+    label = marshmallow.fields.String(example='Open')
260
+    icon = marshmallow.fields.String(example='fa-check')
261
+    hexcolor = marshmallow.fields.String(example='#0000FF')
262
+
263
+
264
+class ContentTypeSchema(marshmallow.Schema):
265
+    slug = marshmallow.fields.String(
266
+        example='pagehtml',
267
+        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
268
+    )
269
+    icon = marshmallow.fields.String(
270
+        example='fa-file-text-o',
271
+        description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon',  # nopep8
272
+    )
273
+    hexcolor = marshmallow.fields.String(
274
+        example="#FF0000",
275
+        description='HTML encoded color associated to the application. Example:#FF0000 for red'  # nopep8
276
+    )
277
+    label = marshmallow.fields.String(
278
+        example='Text Documents'
279
+    )
280
+    creation_label = marshmallow.fields.String(
281
+        example='Write a document'
282
+    )
283
+    available_statuses = marshmallow.fields.Nested(
284
+        StatusSchema,
285
+        many=True
286
+    )
287
+
288
+
289
+class ContentMoveSchema(marshmallow.Schema):
290
+    # TODO - G.M - 30-05-2018 - Read and apply this note
291
+    # Note:
292
+    # if the new workspace is different, then the backend
293
+    # must check if the user is allowed to move to this workspace
294
+    # (the user must be content manager of both workspaces)
295
+    new_parent_id = marshmallow.fields.Int(
296
+        example=42,
297
+        description='id of the new parent content id.'
298
+    )
299
+
300
+
301
+class ContentCreationSchema(marshmallow.Schema):
302
+    label = marshmallow.fields.String(
303
+        example='contract for client XXX',
304
+        description='Title of the content to create'
305
+    )
306
+    content_type_slug = marshmallow.fields.String(
307
+        example='htmlpage',
308
+        validate=OneOf(CONTENT_DEFAULT_TYPE),
309
+    )
310
+
311
+
312
+class ContentDigestSchema(marshmallow.Schema):
313
+    id = marshmallow.fields.Int(example=6)
314
+    slug = marshmallow.fields.Str(example='intervention-report-12')
315
+    parent_id = marshmallow.fields.Int(
316
+        example=34,
317
+        allow_none=True,
318
+        default=None
319
+    )
320
+    workspace_id = marshmallow.fields.Int(
321
+        example=19,
322
+    )
323
+    label = marshmallow.fields.Str(example='Intervention Report 12')
324
+    content_type_slug = marshmallow.fields.Str(
325
+        example='htmlpage',
326
+        validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
327
+    )
328
+    sub_content_type_slug = marshmallow.fields.Nested(
329
+        ContentTypeSchema(only=('slug',)),
330
+        many=True,
331
+        description='list of content types allowed as sub contents. '
332
+                    'This field is required for folder contents, '
333
+                    'set it to empty list in other cases'
334
+    )
335
+    status_slug = marshmallow.fields.Str(
336
+        example='closed-deprecated',
337
+        validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
338
+        description='this slug is found in content_type available statuses',
339
+    )
340
+    is_archived = marshmallow.fields.Bool(example=False)
341
+    is_deleted = marshmallow.fields.Bool(example=False)
342
+    show_in_ui = marshmallow.fields.Bool(
343
+        example=True,
344
+        description='if false, then do not show content in the treeview. '
345
+                    'This may his maybe used for specific contents or '
346
+                    'for sub-contents. Default is True. '
347
+                    'In first version of the API, this field is always True',
348
+    )

+ 19 - 1
tracim/views/core_api/system_controller.py View File

@@ -5,6 +5,7 @@ from tracim.exceptions import NotAuthentificated, InsufficientUserProfile
5 5
 from tracim.lib.utils.authorization import require_profile
6 6
 from tracim.models import Group
7 7
 from tracim.models.applications import applications
8
+from tracim.models.contents import CONTENT_DEFAULT_TYPE
8 9
 
9 10
 try:  # Python 3.5+
10 11
     from http import HTTPStatus
@@ -14,7 +15,7 @@ except ImportError:
14 15
 from tracim import TracimRequest
15 16
 from tracim.extensions import hapic
16 17
 from tracim.views.controllers import Controller
17
-from tracim.views.core_api.schemas import ApplicationSchema
18
+from tracim.views.core_api.schemas import ApplicationSchema, ContentTypeSchema
18 19
 
19 20
 
20 21
 class SystemController(Controller):
@@ -30,6 +31,18 @@ class SystemController(Controller):
30 31
         """
31 32
         return applications
32 33
 
34
+    @hapic.with_api_doc()
35
+    @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
36
+    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
37
+    @require_profile(Group.TIM_USER)
38
+    @hapic.output_body(ContentTypeSchema(many=True),)
39
+    def content_types(self, context, request: TracimRequest, hapic_data=None):
40
+        """
41
+        Get list of alls applications installed in this tracim instance.
42
+        """
43
+
44
+        return CONTENT_DEFAULT_TYPE
45
+
33 46
     def bind(self, configurator: Configurator) -> None:
34 47
         """
35 48
         Create all routes and views using pyramid configurator
@@ -40,3 +53,8 @@ class SystemController(Controller):
40 53
         configurator.add_route('applications', '/system/applications', request_method='GET')  # nopep8
41 54
         configurator.add_view(self.applications, route_name='applications')
42 55
 
56
+        # Content_types
57
+        configurator.add_route('content_types', '/system/content_types', request_method='GET')  # nopep8
58
+        configurator.add_view(self.content_types, route_name='content_types')
59
+
60
+