Passed
Pull Request — develop (#31)
by inkhey
03:45
created

backend.tracim_backend.config.CFG.__init__()   F

Complexity

Conditions 19

Size

Total Lines 460
Code Lines 221

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 221
dl 0
loc 460
rs 0.4199
c 0
b 0
f 0
cc 19
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like backend.tracim_backend.config.CFG.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
import json
3
from urllib.parse import urlparse
4
5
import os
6
from paste.deploy.converters import asbool
7
from tracim_backend.app_models.validator import update_validators
8
from tracim_backend.extensions import app_list
9
from tracim_backend.lib.utils.logger import logger
10
from depot.manager import DepotManager
11
from tracim_backend.app_models.applications import Application
12
from tracim_backend.app_models.contents import CONTENT_TYPES
13
from tracim_backend.app_models.contents import CONTENT_STATUS
14
from tracim_backend.models.data import ActionDescription
15
16
17
class CFG(object):
18
    """Object used for easy access to config file parameters."""
19
20
    def __setattr__(self, key, value):
21
        """
22
        Log-ready setter.
23
24
        Logs all configuration parameters except password.
25
        :param key:
26
        :param value:
27
        :return:
28
        """
29
        if 'PASSWORD' not in key and \
30
                ('URL' not in key or type(value) == str) and \
31
                'CONTENT' not in key:
32
            # We do not show PASSWORD for security reason
33
            # we do not show URL because At the time of configuration setup,
34
            # it can't be evaluated
35
            # We do not show CONTENT in order not to pollute log files
36
            logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value))
37
        else:
38
            logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key))
39
40
        self.__dict__[key] = value
41
42
    def __init__(self, settings):
43
        """Parse configuration file."""
44
45
        ###
46
        # General
47
        ###
48
        backend_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # nopep8
49
        tracim_v2_folder = os.path.dirname(backend_folder)
50
        default_color_config_file_path = os.path.join(tracim_v2_folder, 'color.json')  # nopep8
51
        self.COLOR_CONFIG_FILE_PATH = settings.get(
52
            'color.config_file_path', default_color_config_file_path
53
        )
54
        if not os.path.exists(self.COLOR_CONFIG_FILE_PATH):
55
            raise Exception(
56
                'ERROR: {} file does not exist. '
57
                'please create it or set color.config_file_path'
58
                'with a correct value'.format(self.COLOR_CONFIG_FILE_PATH)
59
            )
60
61
        try:
62
            with open(self.COLOR_CONFIG_FILE_PATH) as json_file:
63
                self.APPS_COLORS = json.load(json_file)
64
        except Exception as e:
65
            raise Exception(
66
                'Error: {} file could not be load as json'.format(self.COLOR_CONFIG_FILE_PATH) # nopep8
67
            ) from e
68
69
        try:
70
            self.APPS_COLORS['primary']
71
        except KeyError as e:
72
            raise Exception(
73
                'Error: primary color is required in {} file'.format(
74
                    self.COLOR_CONFIG_FILE_PATH)  # nopep8
75
            ) from e
76
77
        self._set_default_app()
78
        mandatory_msg = \
79
            'ERROR: {} configuration is mandatory. Set it before continuing.'
80
        self.DEPOT_STORAGE_DIR = settings.get(
81
            'depot_storage_dir',
82
        )
83
        if not self.DEPOT_STORAGE_DIR:
84
            raise Exception(
85
                mandatory_msg.format('depot_storage_dir')
86
            )
87
        self.DEPOT_STORAGE_NAME = settings.get(
88
            'depot_storage_name',
89
        )
90
        if not self.DEPOT_STORAGE_NAME:
91
            raise Exception(
92
                mandatory_msg.format('depot_storage_name')
93
            )
94
        self.PREVIEW_CACHE_DIR = settings.get(
95
            'preview_cache_dir',
96
        )
97
        if not self.PREVIEW_CACHE_DIR:
98
            raise Exception(
99
                'ERROR: preview_cache_dir configuration is mandatory. '
100
                'Set it before continuing.'
101
            )
102
103
        self.DATA_UPDATE_ALLOWED_DURATION = int(settings.get(
104
            'content.update.allowed.duration',
105
            0,
106
        ))
107
108
        self.API_KEY = settings.get(
109
            'api.key',
110
            ''
111
        )
112
113
        self.WEBSITE_TITLE = settings.get(
114
            'website.title',
115
            'TRACIM',
116
        )
117
118
        self.WEBSITE_BASE_URL = settings.get(
119
            'website.base_url',
120
            '',
121
        )
122
        if not self.WEBSITE_BASE_URL:
123
            raise Exception(
124
                'website.base_url is needed in order to have correct path in'
125
                'few place like in email.'
126
                'You should set it with frontend root url.'
127
            )
128
129
        # TODO - G.M - 26-03-2018 - [Cleanup] These params seems deprecated for tracimv2,  # nopep8
130
        # Verify this
131
        #
132
        # self.WEBSITE_HOME_TITLE_COLOR = settings.get(
133
        #     'website.title.color',
134
        #     '#555',
135
        # )
136
        # self.WEBSITE_HOME_IMAGE_PATH = settings.get(
137
        #     '/assets/img/home_illustration.jpg',
138
        # )
139
        # self.WEBSITE_HOME_BACKGROUND_IMAGE_PATH = settings.get(
140
        #     '/assets/img/bg.jpg',
141
        # )
142
        #
143
        self.WEBSITE_SERVER_NAME = settings.get(
144
            'website.server_name',
145
            None,
146
        )
147
148
        if not self.WEBSITE_SERVER_NAME:
149
            self.WEBSITE_SERVER_NAME = urlparse(self.WEBSITE_BASE_URL).hostname
150
            logger.warning(
151
                self,
152
                'NOTE: Generated website.server_name parameter from '
153
                'website.base_url parameter -> {0}'
154
                .format(self.WEBSITE_SERVER_NAME)
155
            )
156
157
        self.WEBSITE_HOME_TAG_LINE = settings.get(
158
            'website.home.tag_line',
159
            '',
160
        )
161
        self.WEBSITE_SUBTITLE = settings.get(
162
            'website.home.subtitle',
163
            '',
164
        )
165
        self.WEBSITE_HOME_BELOW_LOGIN_FORM = settings.get(
166
            'website.home.below_login_form',
167
            '',
168
        )
169
170
        self.WEBSITE_TREEVIEW_CONTENT = settings.get(
171
            'website.treeview.content',
172
        )
173
174
        self.USER_AUTH_TOKEN_VALIDITY = int(settings.get(
175
            'user.auth_token.validity',
176
            '604800',
177
        ))
178
179
        self.DEBUG = asbool(settings.get('debug', False))
180
        # TODO - G.M - 27-03-2018 - [Email] Restore email config
181
        ###
182
        # EMAIL related stuff (notification, reply)
183
        ##
184
185
        self.EMAIL_NOTIFICATION_NOTIFIED_EVENTS = [
186
            ActionDescription.COMMENT,
187
            ActionDescription.CREATION,
188
            ActionDescription.EDITION,
189
            ActionDescription.REVISION,
190
            ActionDescription.STATUS_UPDATE
191
        ]
192
193
        self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
194
            CONTENT_TYPES.Page.slug,
195
            CONTENT_TYPES.Thread.slug,
196
            CONTENT_TYPES.File.slug,
197
            CONTENT_TYPES.Comment.slug,
198
            # CONTENT_TYPES.Folder.slug -- Folder is skipped
199
        ]
200
        if settings.get('email.notification.from'):
201
            raise Exception(
202
                'email.notification.from configuration is deprecated. '
203
                'Use instead email.notification.from.email and '
204
                'email.notification.from.default_label.'
205
            )
206
207
        self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
208
            'email.notification.from.email',
209
            'noreply+{user_id}@trac.im'
210
        )
211
        self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
212
            'email.notification.from.default_label',
213
            'Tracim Notifications'
214
        )
215
        self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
216
            'email.notification.reply_to.email',
217
        )
218
        self.EMAIL_NOTIFICATION_REFERENCES_EMAIL = settings.get(
219
            'email.notification.references.email'
220
        )
221
        self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = settings.get(
222
            'email.notification.content_update.template.html',
223
        )
224
        self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = settings.get(
225
            'email.notification.content_update.template.text',
226
        )
227
        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML = settings.get(
228
            'email.notification.created_account.template.html',
229
            './tracim_backend/templates/mail/created_account_body_html.mak',
230
        )
231
        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT = settings.get(
232
            'email.notification.created_account.template.text',
233
            './tracim_backend/templates/mail/created_account_body_text.mak',
234
        )
235
        self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = settings.get(
236
            'email.notification.content_update.subject',
237
        )
238
        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT = settings.get(
239
            'email.notification.created_account.subject',
240
            '[{website_title}] Created account',
241
        )
242
        self.EMAIL_NOTIFICATION_PROCESSING_MODE = settings.get(
243
            'email.notification.processing_mode',
244
        )
245
246
        self.EMAIL_NOTIFICATION_ACTIVATED = asbool(settings.get(
247
            'email.notification.activated',
248
        ))
249
        self.EMAIL_NOTIFICATION_SMTP_SERVER = settings.get(
250
            'email.notification.smtp.server',
251
        )
252
        self.EMAIL_NOTIFICATION_SMTP_PORT = settings.get(
253
            'email.notification.smtp.port',
254
        )
255
        self.EMAIL_NOTIFICATION_SMTP_USER = settings.get(
256
            'email.notification.smtp.user',
257
        )
258
        self.EMAIL_NOTIFICATION_SMTP_PASSWORD = settings.get(
259
            'email.notification.smtp.password',
260
        )
261
        self.EMAIL_NOTIFICATION_LOG_FILE_PATH = settings.get(
262
            'email.notification.log_file_path',
263
            None,
264
        )
265
266
        self.EMAIL_REPLY_ACTIVATED = asbool(settings.get(
267
            'email.reply.activated',
268
            False,
269
        ))
270
271
        self.EMAIL_REPLY_IMAP_SERVER = settings.get(
272
            'email.reply.imap.server',
273
        )
274
        self.EMAIL_REPLY_IMAP_PORT = settings.get(
275
            'email.reply.imap.port',
276
        )
277
        self.EMAIL_REPLY_IMAP_USER = settings.get(
278
            'email.reply.imap.user',
279
        )
280
        self.EMAIL_REPLY_IMAP_PASSWORD = settings.get(
281
            'email.reply.imap.password',
282
        )
283
        self.EMAIL_REPLY_IMAP_FOLDER = settings.get(
284
            'email.reply.imap.folder',
285
        )
286
        self.EMAIL_REPLY_CHECK_HEARTBEAT = int(settings.get(
287
            'email.reply.check.heartbeat',
288
            60,
289
        ))
290
        self.EMAIL_REPLY_TOKEN = settings.get(
291
            'email.reply.token',
292
        )
293
        self.EMAIL_REPLY_IMAP_USE_SSL = asbool(settings.get(
294
            'email.reply.imap.use_ssl',
295
        ))
296
        self.EMAIL_REPLY_IMAP_USE_IDLE = asbool(settings.get(
297
            'email.reply.imap.use_idle',
298
            True,
299
        ))
300
        self.EMAIL_REPLY_CONNECTION_MAX_LIFETIME = int(settings.get(
301
            'email.reply.connection.max_lifetime',
302
            600,  # 10 minutes
303
        ))
304
        self.EMAIL_REPLY_USE_HTML_PARSING = asbool(settings.get(
305
            'email.reply.use_html_parsing',
306
            True,
307
        ))
308
        self.EMAIL_REPLY_USE_TXT_PARSING = asbool(settings.get(
309
            'email.reply.use_txt_parsing',
310
            True,
311
        ))
312
        self.EMAIL_REPLY_LOCKFILE_PATH = settings.get(
313
            'email.reply.lockfile_path',
314
            ''
315
        )
316
        if not self.EMAIL_REPLY_LOCKFILE_PATH and self.EMAIL_REPLY_ACTIVATED:
317
            raise Exception(
318
                mandatory_msg.format('email.reply.lockfile_path')
319
            )
320
321
        self.EMAIL_PROCESSING_MODE = settings.get(
322
            'email.processing_mode',
323
            'sync',
324
        ).upper()
325
326
        if self.EMAIL_PROCESSING_MODE not in (
327
                self.CST.ASYNC,
328
                self.CST.SYNC,
329
        ):
330
            raise Exception(
331
                'email.processing_mode '
332
                'can ''be "{}" or "{}", not "{}"'.format(
333
                    self.CST.ASYNC,
334
                    self.CST.SYNC,
335
                    self.EMAIL_PROCESSING_MODE,
336
                )
337
            )
338
339
        self.EMAIL_SENDER_REDIS_HOST = settings.get(
340
            'email.async.redis.host',
341
            'localhost',
342
        )
343
        self.EMAIL_SENDER_REDIS_PORT = int(settings.get(
344
            'email.async.redis.port',
345
            6379,
346
        ))
347
        self.EMAIL_SENDER_REDIS_DB = int(settings.get(
348
            'email.async.redis.db',
349
            0,
350
        ))
351
352
        ###
353
        # WSGIDAV (Webdav server)
354
        ###
355
356
        # TODO - G.M - 27-03-2018 - [WebDav] Restore wsgidav config
357
        #self.WSGIDAV_CONFIG_PATH = settings.get(
358
        #    'wsgidav.config_path',
359
        #    'wsgidav.conf',
360
        #)
361
        # TODO: Convert to importlib
362
        # http://stackoverflow.com/questions/41063938/use-importlib-instead-imp-for-non-py-file
363
        #self.wsgidav_config = imp.load_source(
364
        #    'wsgidav_config',
365
        #    self.WSGIDAV_CONFIG_PATH,
366
        #)
367
        # self.WSGIDAV_PORT = self.wsgidav_config.port
368
        # self.WSGIDAV_CLIENT_BASE_URL = settings.get(
369
        #     'wsgidav.client.base_url',
370
        #     None,
371
        # )
372
        #
373
        # if not self.WSGIDAV_CLIENT_BASE_URL:
374
        #     self.WSGIDAV_CLIENT_BASE_URL = \
375
        #         '{0}:{1}'.format(
376
        #             self.WEBSITE_SERVER_NAME,
377
        #             self.WSGIDAV_PORT,
378
        #         )
379
        #     logger.warning(self,
380
        #         'NOTE: Generated wsgidav.client.base_url parameter with '
381
        #         'followings parameters: website.server_name and '
382
        #         'wsgidav.conf port'.format(
383
        #             self.WSGIDAV_CLIENT_BASE_URL,
384
        #         )
385
        #     )
386
        #
387
        # if not self.WSGIDAV_CLIENT_BASE_URL.endswith('/'):
388
        #     self.WSGIDAV_CLIENT_BASE_URL += '/'
389
390
        # TODO - G.M - 27-03-2018 - [Caldav] Restore radicale config
391
        ###
392
        # RADICALE (Caldav server)
393
        ###
394
        # self.RADICALE_SERVER_HOST = settings.get(
395
        #     'radicale.server.host',
396
        #     '127.0.0.1',
397
        # )
398
        # self.RADICALE_SERVER_PORT = int(settings.get(
399
        #     'radicale.server.port',
400
        #     5232,
401
        # ))
402
        # # Note: Other parameters needed to work in SSL (cert file, etc)
403
        # self.RADICALE_SERVER_SSL = asbool(settings.get(
404
        #     'radicale.server.ssl',
405
        #     False,
406
        # ))
407
        # self.RADICALE_SERVER_FILE_SYSTEM_FOLDER = settings.get(
408
        #     'radicale.server.filesystem.folder',
409
        # )
410
        # if not self.RADICALE_SERVER_FILE_SYSTEM_FOLDER:
411
        #     raise Exception(
412
        #         mandatory_msg.format('radicale.server.filesystem.folder')
413
        #     )
414
        # self.RADICALE_SERVER_ALLOW_ORIGIN = settings.get(
415
        #     'radicale.server.allow_origin',
416
        #     None,
417
        # )
418
        # if not self.RADICALE_SERVER_ALLOW_ORIGIN:
419
        #     self.RADICALE_SERVER_ALLOW_ORIGIN = self.WEBSITE_BASE_URL
420
        #     logger.warning(self,
421
        #         'NOTE: Generated radicale.server.allow_origin parameter with '
422
        #         'followings parameters: website.base_url ({0})'
423
        #         .format(self.WEBSITE_BASE_URL)
424
        #     )
425
        #
426
        # self.RADICALE_SERVER_REALM_MESSAGE = settings.get(
427
        #     'radicale.server.realm_message',
428
        #     'Tracim Calendar - Password Required',
429
        # )
430
        #
431
        # self.RADICALE_CLIENT_BASE_URL_HOST = settings.get(
432
        #     'radicale.client.base_url.host',
433
        #     'http://{}:{}'.format(
434
        #         self.RADICALE_SERVER_HOST,
435
        #         self.RADICALE_SERVER_PORT,
436
        #     ),
437
        # )
438
        #
439
        # self.RADICALE_CLIENT_BASE_URL_PREFIX = settings.get(
440
        #     'radicale.client.base_url.prefix',
441
        #     '/',
442
        # )
443
        # # Ensure finished by '/'
444
        # if '/' != self.RADICALE_CLIENT_BASE_URL_PREFIX[-1]:
445
        #     self.RADICALE_CLIENT_BASE_URL_PREFIX += '/'
446
        # if '/' != self.RADICALE_CLIENT_BASE_URL_PREFIX[0]:
447
        #     self.RADICALE_CLIENT_BASE_URL_PREFIX \
448
        #         = '/' + self.RADICALE_CLIENT_BASE_URL_PREFIX
449
        #
450
        # if not self.RADICALE_CLIENT_BASE_URL_HOST:
451
        #     logger.warning(self,
452
        #         'Generated radicale.client.base_url.host parameter with '
453
        #         'followings parameters: website.server_name -> {}'
454
        #         .format(self.WEBSITE_SERVER_NAME)
455
        #     )
456
        #     self.RADICALE_CLIENT_BASE_URL_HOST = self.WEBSITE_SERVER_NAME
457
        #
458
        # self.RADICALE_CLIENT_BASE_URL_TEMPLATE = '{}{}'.format(
459
        #     self.RADICALE_CLIENT_BASE_URL_HOST,
460
        #     self.RADICALE_CLIENT_BASE_URL_PREFIX,
461
        # )
462
        self.PREVIEW_JPG_RESTRICTED_DIMS = asbool(settings.get(
463
            'preview.jpg.restricted_dims', False
464
        ))
465
        preview_jpg_allowed_dims_str = settings.get('preview.jpg.allowed_dims', '')  # nopep8
466
        allowed_dims = []
467
        if preview_jpg_allowed_dims_str:
468
            for sizes in preview_jpg_allowed_dims_str.split(','):
469
                parts = sizes.split('x')
470
                assert len(parts) == 2
471
                width, height = parts
472
                assert width.isdecimal()
473
                assert height.isdecimal()
474
                size = PreviewDim(int(width), int(height))
475
                allowed_dims.append(size)
476
477
        if not allowed_dims:
478
            size = PreviewDim(256, 256)
479
            allowed_dims.append(size)
480
481
        self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims
482
483
        self.FRONTEND_SERVE = asbool(settings.get(
484
            'frontend.serve', False
485
        ))
486
487
        # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
488
        # is probably in frontend subfolder
489
        # of tracim_v2 parent of both backend and frontend
490
        frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
491
492
        self.FRONTEND_DIST_FOLDER_PATH = settings.get(
493
            'frontend.dist_folder_path', frontend_dist_folder
494
        )
495
496
        # INFO - G.M - 2018-08-06 - We check dist folder existence
497
        if self.FRONTEND_SERVE and not os.path.isdir(self.FRONTEND_DIST_FOLDER_PATH):  # nopep8
498
            raise Exception(
499
                'ERROR: {} folder does not exist as folder. '
500
                'please set frontend.dist_folder.path'
501
                'with a correct value'.format(self.FRONTEND_DIST_FOLDER_PATH)
502
            )
503
504
    def configure_filedepot(self):
505
506
        # TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var
507
        # of tracim_backend, Be careful DepotManager is a Singleton !
508
509
        depot_storage_name = self.DEPOT_STORAGE_NAME
510
        depot_storage_path = self.DEPOT_STORAGE_DIR
511
        depot_storage_settings = {'depot.storage_path': depot_storage_path}
512
        DepotManager.configure(
513
            depot_storage_name,
514
            depot_storage_settings,
515
        )
516
517
    def _set_default_app(self):
518
        calendar = Application(
519
            label='Calendar',
520
            slug='calendar',
521
            fa_icon='calendar',
522
            is_active=False,
523
            config={},
524
            main_route='/#/workspaces/{workspace_id}/calendar',
525
            app_config=self
526
        )
527
528
        thread = Application(
529
            label='Threads',
530
            slug='contents/thread',
531
            fa_icon='comments-o',
532
            is_active=True,
533
            config={},
534
            main_route='/#/workspaces/{workspace_id}/contents?type=thread',
535
            app_config=self
536
        )
537
        thread.add_content_type(
538
            slug='thread',
539
            label='Thread',
540
            creation_label='Discuss about a topic',
541
            available_statuses=CONTENT_STATUS.get_all(),
542
        )
543
544
        folder = Application(
545
            label='Folder',
546
            slug='contents/folder',
547
            fa_icon='folder-open-o',
548
            is_active=True,
549
            config={},
550
            main_route='',
551
            app_config=self
552
        )
553
        folder.add_content_type(
554
            slug='folder',
555
            label='Folder',
556
            creation_label='Create a folder',
557
            available_statuses=CONTENT_STATUS.get_all(),
558
            allow_sub_content=True,
559
        )
560
561
        _file = Application(
562
            label='Files',
563
            slug='contents/file',
564
            fa_icon='paperclip',
565
            is_active=True,
566
            config={},
567
            main_route='/#/workspaces/{workspace_id}/contents?type=file',
568
            app_config=self,
569
        )
570
        _file.add_content_type(
571
            slug='file',
572
            label='File',
573
            creation_label='Upload a file',
574
            available_statuses=CONTENT_STATUS.get_all(),
575
        )
576
577
        markdownpluspage = Application(
578
            label='Markdown Plus Documents',
579
            # TODO - G.M - 24-05-2018 - Check label
580
            slug='content/markdownpluspage',
581
            fa_icon='file-code-o',
582
            is_active=False,
583
            config={},
584
            main_route='/#/workspaces/{workspace_id}/contents?type=markdownpluspage',
585
            # nopep8
586
            app_config=self,
587
        )
588
        markdownpluspage.add_content_type(
589
            slug='markdownpage',
590
            label='Rich Markdown File',
591
            creation_label='Create a Markdown document',
592
            available_statuses=CONTENT_STATUS.get_all(),
593
        )
594
595
        html_documents = Application(
596
            label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
597
            slug='contents/html-document',
598
            fa_icon='file-text-o',
599
            is_active=True,
600
            config={},
601
            main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
602
            app_config=self
603
        )
604
        html_documents.add_content_type(
605
            slug='html-document',
606
            label='Text Document',
607
            creation_label='Write a document',
608
            available_statuses=CONTENT_STATUS.get_all(),
609
            slug_alias=['page']
610
        )
611
612
        # TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var
613
        # of tracim_backend, Be careful app_list is a global_var
614
        app_list.clear()
615
        app_list.extend([
616
            html_documents,
617
            markdownpluspage,
618
            _file,
619
            thread,
620
            folder,
621
            calendar,
622
        ])
623
        # TODO - G.M - 2018-08-08 - We need to update validators each time
624
        # app_list is updated.
625
        update_validators()
626
627
    class CST(object):
628
        ASYNC = 'ASYNC'
629
        SYNC = 'SYNC'
630
631
        TREEVIEW_FOLDERS = 'folders'
632
        TREEVIEW_ALL = 'all'
633
634
635
class PreviewDim(object):
636
637
    def __init__(self, width: int, height: int) -> None:
638
        self.width = width
639
        self.height = height
640
641
    def __repr__(self):
642
        return "<PreviewDim width:{width} height:{height}>".format(
643
            width=self.width,
644
            height=self.height,
645
        )
646