瀏覽代碼

refactor contentType and contentStatus legacy code + content_type endpoint

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

+ 9 - 1
tracim/exceptions.py 查看文件

94
 
94
 
95
 
95
 
96
 class UserNotExist(TracimException):
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 查看文件

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 查看文件

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
 class ContentChecker(object):
541
 class ContentChecker(object):
537
 
542
 

+ 32 - 2
tracim/tests/functional/test_system.py 查看文件

95
         )
95
         )
96
         res = self.testapp.get('/api/v2/system/content_types', status=200)
96
         res = self.testapp.get('/api/v2/system/content_types', status=200)
97
         res = res.json_body
97
         res = res.json_body
98
+
98
         content_type = res[0]
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
         assert content_type['icon'] == 'file-text-o'
128
         assert content_type['icon'] == 'file-text-o'
101
         assert content_type['hexcolor'] == '#3f52e3'
129
         assert content_type['hexcolor'] == '#3f52e3'
102
-        assert content_type['label'] == 'Text Documents'
130
+        assert content_type['label'] == 'Text Document'
103
         assert content_type['creation_label'] == 'Write a document'
131
         assert content_type['creation_label'] == 'Write a document'
104
         assert 'available_statuses' in content_type
132
         assert 'available_statuses' in content_type
133
+        assert len(content_type['available_statuses']) == 4
134
+
105
         # TODO - G.M - 29-05-2018 - Better check for available_statuses
135
         # TODO - G.M - 29-05-2018 - Better check for available_statuses
106
 
136
 
107
     def test_api__get_content_types__err_401__unregistered_user(self):
137
     def test_api__get_content_types__err_401__unregistered_user(self):

+ 154 - 2
tracim/views/core_api/schemas.py 查看文件

4
 from marshmallow.validate import OneOf
4
 from marshmallow.validate import OneOf
5
 
5
 
6
 from tracim.models.auth import Profile
6
 from tracim.models.auth import Profile
7
+from tracim.models.contents import CONTENT_DEFAULT_TYPE, GlobalStatus, CONTENT_DEFAULT_STATUS
7
 from tracim.models.context_models import LoginCredentials
8
 from tracim.models.context_models import LoginCredentials
8
 from tracim.models.data import UserRoleInWorkspace
9
 from tracim.models.data import UserRoleInWorkspace
9
 
10
 
65
         description = 'User account of Tracim'
66
         description = 'User account of Tracim'
66
 
67
 
67
 
68
 
69
+# Path Schemas
70
+
71
+
68
 class UserIdPathSchema(marshmallow.Schema):
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
 class WorkspaceIdPathSchema(marshmallow.Schema):
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
 class BasicAuthSchema(marshmallow.Schema):
125
 class BasicAuthSchema(marshmallow.Schema):
194
 
243
 
195
     class Meta:
244
     class Meta:
196
         description = 'Tracim Application informations'
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 查看文件

5
 from tracim.lib.utils.authorization import require_profile
5
 from tracim.lib.utils.authorization import require_profile
6
 from tracim.models import Group
6
 from tracim.models import Group
7
 from tracim.models.applications import applications
7
 from tracim.models.applications import applications
8
+from tracim.models.contents import CONTENT_DEFAULT_TYPE
8
 
9
 
9
 try:  # Python 3.5+
10
 try:  # Python 3.5+
10
     from http import HTTPStatus
11
     from http import HTTPStatus
14
 from tracim import TracimRequest
15
 from tracim import TracimRequest
15
 from tracim.extensions import hapic
16
 from tracim.extensions import hapic
16
 from tracim.views.controllers import Controller
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
 class SystemController(Controller):
21
 class SystemController(Controller):
30
         """
31
         """
31
         return applications
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
     def bind(self, configurator: Configurator) -> None:
46
     def bind(self, configurator: Configurator) -> None:
34
         """
47
         """
35
         Create all routes and views using pyramid configurator
48
         Create all routes and views using pyramid configurator
40
         configurator.add_route('applications', '/system/applications', request_method='GET')  # nopep8
53
         configurator.add_route('applications', '/system/applications', request_method='GET')  # nopep8
41
         configurator.add_view(self.applications, route_name='applications')
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
+