config.py 22KB

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