app_cfg.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. # -*- coding: utf-8 -*-
  2. """
  3. Global configuration file for TG2-specific settings in tracim.
  4. This file complements development/deployment.ini.
  5. Please note that **all the argument values are strings**. If you want to
  6. convert them into boolean, for example, you should use the
  7. :func:`paste.deploy.converters.asbool` function, as in::
  8. from paste.deploy.converters import asbool
  9. setting = asbool(global_conf.get('the_setting'))
  10. """
  11. import imp
  12. import importlib
  13. from urllib.parse import urlparse
  14. import tg
  15. from paste.deploy.converters import asbool
  16. from tg.configuration.milestones import environment_loaded
  17. from tgext.asyncjob.trackers.memory import MemoryProgressTracker
  18. from tgext.asyncjob.trackers.redisdb import RedisProgressTracker
  19. from tgext.pluggable import plug
  20. from tgext.pluggable import replace_template
  21. from tg.i18n import lazy_ugettext as l_
  22. import tracim
  23. from tracim import model
  24. from tracim.config import TracimAppConfig
  25. from tracim.lib import app_globals, helpers
  26. from tracim.lib.auth.wrapper import AuthConfigWrapper
  27. from tracim.lib.base import logger
  28. from tracim.lib.daemons import DaemonsManager
  29. from tracim.lib.daemons import RadicaleDaemon
  30. from tracim.lib.daemons import WsgiDavDaemon
  31. from tracim.model.data import ActionDescription
  32. from tracim.model.data import ContentType
  33. base_config = TracimAppConfig()
  34. base_config.renderers = []
  35. base_config.use_toscawidgets = False
  36. base_config.use_toscawidgets2 = True
  37. base_config.package = tracim
  38. #Enable json in expose
  39. base_config.renderers.append('json')
  40. #Enable genshi in expose to have a lingua franca for extensions and pluggable apps
  41. #you can remove this if you don't plan to use it.
  42. base_config.renderers.append('genshi')
  43. #Set the default renderer
  44. base_config.default_renderer = 'mako'
  45. base_config.renderers.append('mako')
  46. #Configure the base SQLALchemy Setup
  47. base_config.use_sqlalchemy = True
  48. base_config.model = tracim.model
  49. base_config.DBSession = tracim.model.DBSession
  50. # This value can be modified by tracim.lib.auth.wrapper.AuthConfigWrapper but have to be specified before
  51. base_config.auth_backend = 'sqlalchemy'
  52. # base_config.flash.cookie_name
  53. # base_config.flash.default_status -> Default message status if not specified (ok by default)
  54. base_config['flash.template'] = '''
  55. <div class="alert alert-${status}" style="margin-top: 1em;">
  56. <button type="button" class="close" data-dismiss="alert">&times;</button>
  57. <div id="${container_id}">
  58. <img src="/assets/icons/32x32/status/flash-${status}.png"/>
  59. ${message}
  60. </div>
  61. </div>
  62. '''
  63. # -> string.Template instance used as the flash template when rendered from server side, will receive $container_id, $message and $status variables.
  64. # flash.js_call -> javascript code which will be run when displaying the flash from javascript. Default is webflash.render(), you can use webflash.payload() to retrieve the message and show it with your favourite library.
  65. # flash.js_template -> string.Template instance used to replace full javascript support for flash messages. When rendering flash message for javascript usage the following code will be used instead of providing the standard webflash object. If you replace js_template you must also ensure cookie parsing and delete it for already displayed messages. The template will receive: $container_id, $cookie_name, $js_call variables.
  66. base_config['templating.genshi.name_constant_patch'] = True
  67. # Configure the authentication backend
  68. # YOU MUST CHANGE THIS VALUE IN PRODUCTION TO SECURE YOUR APP
  69. base_config.sa_auth.cookie_secret = "3283411b-1904-4554-b0e1-883863b53080"
  70. # INFO - This is the way to specialize the resetpassword email properties
  71. # plug(base_config, 'resetpassword', None, mail_subject=reset_password_email_subject)
  72. plug(base_config, 'resetpassword', 'reset_password')
  73. replace_template(base_config, 'resetpassword.templates.index', 'tracim.templates.reset_password_index')
  74. replace_template(base_config, 'resetpassword.templates.change_password', 'mako:tracim.templates.reset_password_change_password')
  75. daemons = DaemonsManager()
  76. def start_daemons(manager: DaemonsManager):
  77. """
  78. Sart Tracim daemons
  79. """
  80. from tg import config
  81. # Don't start daemons if they are disabled
  82. if 'disable_daemons' in config and config['disable_daemons']:
  83. return
  84. manager.run('radicale', RadicaleDaemon)
  85. manager.run('webdav', WsgiDavDaemon)
  86. environment_loaded.register(lambda: start_daemons(daemons))
  87. # Note: here are fake translatable strings that allow to translate messages for reset password email content
  88. duplicated_email_subject = l_('Password reset request')
  89. duplicated_email_body = l_('''
  90. We've received a request to reset the password for this account.
  91. Please click this link to reset your password:
  92. %(password_reset_link)s
  93. If you no longer wish to make the above change, or if you did not initiate this request, please disregard and/or delete this e-mail.
  94. ''')
  95. #######
  96. #
  97. # INFO - D.A. - 2014-10-31
  98. # The following code is a dirty way to integrate translation for resetpassword tgapp in tracim
  99. # TODO - Integrate these translations into tgapp-resetpassword
  100. #
  101. l_('New password')
  102. l_('Confirm new password')
  103. l_('Save new password')
  104. l_('Email address')
  105. l_('Send Request')
  106. l_('Password reset request sent')
  107. l_('Invalid password reset request')
  108. l_('Password reset request timed out')
  109. l_('Invalid password reset request')
  110. l_('Password changed successfully')
  111. l_('''
  112. We've received a request to reset the password for this account.
  113. Please click this link to reset your password:
  114. %(password_reset_link)s
  115. If you no longer wish to make the above change, or if you did not initiate this request, please disregard and/or delete this e-mail.
  116. ''')
  117. class CFG(object):
  118. """
  119. Singleton used for easy access to config file parameters
  120. """
  121. _instance = None
  122. @classmethod
  123. def get_instance(cls) -> 'CFG':
  124. if not CFG._instance:
  125. CFG._instance = CFG()
  126. return CFG._instance
  127. def __setattr__(self, key, value):
  128. """
  129. Log-ready setter. this is used for logging configuration (every parameter except password)
  130. :param key:
  131. :param value:
  132. :return:
  133. """
  134. if 'PASSWORD' not in key and 'URL' not in key and 'CONTENT' not in key:
  135. # We do not show PASSWORD for security reason
  136. # we do not show URL because the associated config uses tg.lurl() which is evaluated when at display time.
  137. # At the time of configuration setup, it can't be evaluated
  138. # We do not show CONTENT in order not to pollute log files
  139. logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value))
  140. else:
  141. logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key))
  142. self.__dict__[key] = value
  143. def __init__(self):
  144. self.DATA_UPDATE_ALLOWED_DURATION = int(tg.config.get('content.update.allowed.duration', 0))
  145. self.WEBSITE_TITLE = tg.config.get('website.title', 'TRACIM')
  146. self.WEBSITE_HOME_TITLE_COLOR = tg.config.get('website.title.color', '#555')
  147. self.WEBSITE_HOME_IMAGE_URL = tg.lurl('/assets/img/home_illustration.jpg')
  148. self.WEBSITE_HOME_BACKGROUND_IMAGE_URL = tg.lurl('/assets/img/bg.jpg')
  149. self.WEBSITE_BASE_URL = tg.config.get('website.base_url', '')
  150. self.WEBSITE_SERVER_NAME = tg.config.get('website.server_name', None)
  151. if not self.WEBSITE_SERVER_NAME:
  152. self.WEBSITE_SERVER_NAME = urlparse(self.WEBSITE_BASE_URL).hostname
  153. logger.warning(
  154. self,
  155. 'NOTE: Generated website.server_name parameter from '
  156. 'website.base_url parameter -> {0}'
  157. .format(self.WEBSITE_SERVER_NAME)
  158. )
  159. self.WEBSITE_HOME_TAG_LINE = tg.config.get('website.home.tag_line', '')
  160. self.WEBSITE_SUBTITLE = tg.config.get('website.home.subtitle', '')
  161. self.WEBSITE_HOME_BELOW_LOGIN_FORM = tg.config.get('website.home.below_login_form', '')
  162. if tg.config.get('email.notification.from'):
  163. raise Exception(
  164. 'email.notification.from configuration is deprecated. '
  165. 'Use instead email.notification.from.email and '
  166. 'email.notification.from.default_label.'
  167. )
  168. self.EMAIL_NOTIFICATION_FROM_EMAIL = \
  169. tg.config.get('email.notification.from.email')
  170. self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = \
  171. tg.config.get('email.notification.from.default_label')
  172. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = tg.config.get('email.notification.content_update.template.html')
  173. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = tg.config.get('email.notification.content_update.template.text')
  174. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML = tg.config.get(
  175. 'email.notification.created_account.template.html',
  176. './tracim/templates/mail/created_account_body_html.mak',
  177. )
  178. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT = tg.config.get(
  179. 'email.notification.created_account.template.text',
  180. './tracim/templates/mail/created_account_body_text.mak',
  181. )
  182. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = tg.config.get('email.notification.content_update.subject')
  183. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT = tg.config.get(
  184. 'email.notification.created_account.subject',
  185. '[{website_title}] Created account',
  186. )
  187. self.EMAIL_NOTIFICATION_PROCESSING_MODE = tg.config.get('email.notification.processing_mode')
  188. self.EMAIL_NOTIFICATION_ACTIVATED = asbool(tg.config.get('email.notification.activated'))
  189. self.EMAIL_NOTIFICATION_SMTP_SERVER = tg.config.get('email.notification.smtp.server')
  190. self.EMAIL_NOTIFICATION_SMTP_PORT = tg.config.get('email.notification.smtp.port')
  191. self.EMAIL_NOTIFICATION_SMTP_USER = tg.config.get('email.notification.smtp.user')
  192. self.EMAIL_NOTIFICATION_SMTP_PASSWORD = tg.config.get('email.notification.smtp.password')
  193. self.TRACKER_JS_PATH = tg.config.get('js_tracker_path')
  194. self.TRACKER_JS_CONTENT = self.get_tracker_js_content(self.TRACKER_JS_PATH)
  195. self.WEBSITE_TREEVIEW_CONTENT = tg.config.get('website.treeview.content')
  196. self.EMAIL_NOTIFICATION_NOTIFIED_EVENTS = [
  197. ActionDescription.COMMENT,
  198. ActionDescription.CREATION,
  199. ActionDescription.EDITION,
  200. ActionDescription.REVISION,
  201. ActionDescription.STATUS_UPDATE
  202. ]
  203. self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
  204. ContentType.Page,
  205. ContentType.Thread,
  206. ContentType.File,
  207. ContentType.Comment,
  208. # ContentType.Folder -- Folder is skipped
  209. ]
  210. self.RADICALE_SERVER_HOST = tg.config.get('radicale.server.host', '0.0.0.0')
  211. self.RADICALE_SERVER_PORT = int(
  212. tg.config.get('radicale.server.port', 5232)
  213. )
  214. # Note: Other parameters needed to work in SSL (cert file, etc)
  215. self.RADICALE_SERVER_SSL = asbool(tg.config.get('radicale.server.ssl', False))
  216. self.RADICALE_SERVER_FILE_SYSTEM_FOLDER = tg.config.get(
  217. 'radicale.server.filesystem.folder',
  218. '~/.config/radicale/collections'
  219. )
  220. self.RADICALE_SERVER_ALLOW_ORIGIN = tg.config.get(
  221. 'radicale.server.allow_origin',
  222. None,
  223. )
  224. if not self.RADICALE_SERVER_ALLOW_ORIGIN:
  225. self.RADICALE_SERVER_ALLOW_ORIGIN = self.WEBSITE_BASE_URL
  226. logger.warning(
  227. self,
  228. 'NOTE: Generated radicale.server.allow_origin parameter with '
  229. 'followings parameters: website.base_url ({0})'
  230. .format(self.WEBSITE_BASE_URL)
  231. )
  232. self.RADICALE_SERVER_REALM_MESSAGE = tg.config.get(
  233. 'radicale.server.realm_message',
  234. 'Tracim Calendar - Password Required',
  235. )
  236. self.RADICALE_CLIENT_BASE_URL_TEMPLATE = \
  237. tg.config.get('radicale.client.base_url', None)
  238. if not self.RADICALE_CLIENT_BASE_URL_TEMPLATE:
  239. self.RADICALE_CLIENT_BASE_URL_TEMPLATE = \
  240. 'http://{0}:{1}'.format(
  241. self.WEBSITE_SERVER_NAME,
  242. self.RADICALE_SERVER_PORT,
  243. )
  244. logger.warning(
  245. self,
  246. 'NOTE: Generated radicale.client.base_url parameter with '
  247. 'followings parameters: website.server_name, '
  248. 'radicale.server.port -> {0}'
  249. .format(self.RADICALE_CLIENT_BASE_URL_TEMPLATE)
  250. )
  251. self.USER_AUTH_TOKEN_VALIDITY = int(tg.config.get(
  252. 'user.auth_token.validity',
  253. '604800',
  254. ))
  255. self.WSGIDAV_CONFIG_PATH = tg.config.get(
  256. 'wsgidav.config_path',
  257. 'wsgidav.conf',
  258. )
  259. # TODO: Convert to importlib (cf http://stackoverflow.com/questions/41063938/use-importlib-instead-imp-for-non-py-file)
  260. self.wsgidav_config = imp.load_source(
  261. 'wsgidav_config',
  262. self.WSGIDAV_CONFIG_PATH,
  263. )
  264. self.WSGIDAV_PORT = self.wsgidav_config.port
  265. self.WSGIDAV_CLIENT_BASE_URL = \
  266. tg.config.get('wsgidav.client.base_url', None)
  267. if not self.WSGIDAV_CLIENT_BASE_URL:
  268. self.WSGIDAV_CLIENT_BASE_URL = \
  269. '{0}:{1}'.format(
  270. self.WEBSITE_SERVER_NAME,
  271. self.WSGIDAV_PORT,
  272. )
  273. logger.warning(
  274. self,
  275. 'NOTE: Generated wsgidav.client.base_url parameter with '
  276. 'followings parameters: website.server_name and '
  277. 'wsgidav.conf port'.format(
  278. self.WSGIDAV_CLIENT_BASE_URL,
  279. )
  280. )
  281. if not self.WSGIDAV_CLIENT_BASE_URL.endswith('/'):
  282. self.WSGIDAV_CLIENT_BASE_URL += '/'
  283. self.ASYNC_JOB_TRACKER = tg.config.get(
  284. 'asyncjob.tracker',
  285. 'memory',
  286. )
  287. if self.ASYNC_JOB_TRACKER not in ('memory', 'redis'):
  288. raise Exception(
  289. 'asyncjob.tracker configuration '
  290. 'can ''be "memory" or "redis", not "{0}"'.format(
  291. self.ASYNC_JOB_TRACKER,
  292. )
  293. )
  294. if self.ASYNC_JOB_TRACKER == 'redis':
  295. self.ASYNC_JOB_TRACKER_REDIS_HOST = tg.config.get(
  296. 'asyncjob.tracker.redis.host',
  297. 'localhost',
  298. )
  299. self.ASYNC_JOB_TRACKER_REDIS_PORT = int(tg.config.get(
  300. 'asyncjob.tracker.redis.port',
  301. 6379,
  302. ))
  303. self.ASYNC_JOB_TRACKER_REDIS_DB = int(tg.config.get(
  304. 'asyncjob.tracker.redis.db',
  305. 15,
  306. ))
  307. def get_tracker_js_content(self, js_tracker_file_path = None):
  308. js_tracker_file_path = tg.config.get('js_tracker_path', None)
  309. if js_tracker_file_path:
  310. logger.info(self, 'Reading JS tracking code from file {}'.format(js_tracker_file_path))
  311. with open (js_tracker_file_path, 'r') as js_file:
  312. data = js_file.read()
  313. return data
  314. else:
  315. return ''
  316. class CST(object):
  317. ASYNC = 'ASYNC'
  318. SYNC = 'SYNC'
  319. TREEVIEW_FOLDERS = 'folders'
  320. TREEVIEW_ALL = 'all'
  321. #######
  322. #
  323. # INFO - D.A. - 2014-11-05
  324. # Allow to process asynchronous tasks
  325. # This is used for email notifications
  326. #
  327. # import tgext.asyncjob
  328. # tgext.asyncjob.plugme(base_config)
  329. #
  330. # OR
  331. #
  332. # plug(base_config, 'tgext.asyncjob', app_globals=base_config)
  333. #
  334. # OR
  335. #
  336. # Add some variable to each templates
  337. base_config.variable_provider = lambda: {
  338. 'CFG': CFG.get_instance()
  339. }
  340. def plug_asyncjob():
  341. cfg = CFG.get_instance()
  342. # # Manual creation of async job tracker to be able to log it
  343. async_job_tracker = cfg.ASYNC_JOB_TRACKER
  344. if async_job_tracker == 'redis':
  345. async_job_progress_tracker = RedisProgressTracker(
  346. host=cfg.ASYNC_JOB_TRACKER_REDIS_HOST,
  347. port=cfg.ASYNC_JOB_TRACKER_REDIS_PORT,
  348. db=cfg.ASYNC_JOB_TRACKER_REDIS_DB,
  349. )
  350. else:
  351. async_job_progress_tracker = MemoryProgressTracker()
  352. logger.info(
  353. cfg,
  354. 'Async job track using {0}'.format(str(async_job_progress_tracker)),
  355. )
  356. plug(
  357. base_config,
  358. 'tgext.asyncjob',
  359. progress_tracker=async_job_progress_tracker,
  360. )
  361. environment_loaded.register(lambda: plug_asyncjob())