Passed
Pull Request — develop (#31)
by inkhey
02:43
created

PreviewDim.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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