__init__.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import bleach
  2. import bottle
  3. import commonmark
  4. import dateutil.parser
  5. import functools
  6. import importlib
  7. import json
  8. import os
  9. import re
  10. import urllib.parse
  11. from string import Template
  12. from urllib.parse import urlparse
  13. # This is used to export the bottle object for the WSGI server
  14. application = bottle.app()
  15. # Load settings for this app
  16. application.config.load_config('settings.ini')
  17. # Set a "settings" variable to be used from other modules without having to
  18. # import the "application" object
  19. settings = application.config
  20. # "bleach" module is used to sanitize the HTML output during CommmonMark->HTML
  21. # conversion. The library has only a very restrictive list of white-listed
  22. # tags, so we add some more here.
  23. bleach.sanitizer.ALLOWED_TAGS += [
  24. 'br', 'colgroup', 'col', 'div', 'dd', 'dl', 'dt', 'figure', 'figcaption',
  25. 'h1', 'h2', 'h3', 'hr', 'img', 'link', 'object', 'p', 'pre', 'source', 'span',
  26. 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'var', 'video' ]
  27. bleach.sanitizer.ALLOWED_ATTRIBUTES.update ({
  28. 'a': [ 'class', 'href' ],
  29. 'div': [ 'class' ],
  30. 'img': [ 'alt', 'class', 'src', 'title' ],
  31. 'link': [ 'href', 'rel' ],
  32. 'object': [ 'data', 'type', 'typemustmatch' ],
  33. 'source': [ 'src' ],
  34. 'video': [ 'controls', 'height', 'width' ]
  35. })
  36. from dokk import graph, user
  37. import dokk.sparql as sparql
  38. def url(name, *anons, **parameters):
  39. """
  40. Return a URL string corresponding to a Bottle route, given its arguments.
  41. We use this function because it's shorter than calling application.router.build
  42. everywhere, especially in templates.
  43. """
  44. # Escape all hashes symbols (#) because they are not passed on by the browser
  45. for parameter in parameters.keys():
  46. if isinstance(parameters[parameter], str):
  47. parameters[parameter] = parameters[parameter].replace('#', '%23')
  48. return application.router.build(name, *anons, **parameters)
  49. def clear_url(url):
  50. """
  51. Return only "host:port/path" of the given URL.
  52. This is mostly useful to display user-friendly URL names
  53. to the users.
  54. """
  55. url = urlparse(url)
  56. if url.hostname == None:
  57. return ''
  58. clean_url = url.hostname
  59. if url.port != None:
  60. clean_url += ':' + url.port
  61. # Do not write if it's a single /
  62. if url.path != '/':
  63. clean_url += url.path
  64. return clean_url
  65. def dump(variable, indent=None):
  66. """
  67. Dump a variable as JSON.
  68. Mostly useful for debugging.
  69. """
  70. return json.dumps(variable, indent=indent)
  71. def query(id, **parameters):
  72. """
  73. A function for templates for returning a query results.
  74. :param id: ID of the Query to execute.
  75. :param parameters: List of parameters for the query.
  76. """
  77. # Retrieve the Query
  78. query_string = graph.get_query(id)['content']
  79. # Substitute parameters
  80. query_string = Template(query_string).substitute(**parameters)
  81. try:
  82. return sparql.query_public(query_string)
  83. except Exception as e:
  84. return None
  85. def read_archive(filename):
  86. """
  87. Read a file from the archive and return its content.
  88. """
  89. filepath = settings['dokk.archive'] + '/' + filename
  90. if not os.path.isfile(filepath):
  91. return None
  92. with open(filepath, mode='r') as f:
  93. return f.read()
  94. # template() functions for rendering routes
  95. template = functools.partial(
  96. bottle.jinja2_template,
  97. template_lookup = [ '/srv/dokk/dokk/templates' ],
  98. template_settings = {
  99. 'filters': {
  100. 'clear_url': clear_url,
  101. 'commonmark2html': lambda text: bleach.clean(bleach.linkify(commonmark.commonmark(text or ''))),
  102. 'simple_date': lambda date: dateutil.parser.parse(date).strftime('%H:%M, %d %b %Y')
  103. },
  104. 'globals': {
  105. # Variables
  106. # ...
  107. # Functions
  108. 'dump': dump,
  109. 'query': query,
  110. 'url': url,
  111. 'user': lambda: user.get_from_session(),
  112. 'signedin': lambda: user.is_signedin()
  113. },
  114. 'autoescape': True
  115. })
  116. class NodeTemplate(bottle.Jinja2Template):
  117. """
  118. This is an extension of Bottle's Jinja2Template class, that is used to
  119. load templates. What we're doing here is override the loader() function
  120. to load templates from the database instead of the filesystem.
  121. This class is used only for loading nodes' templates.
  122. See: https://github.com/bottlepy/bottle/blob/a454029f6e8a087e5cb570eb6ee36c2087d26e4d/bottle.py#L3914
  123. https://jinja.pocoo.org/docs/2.10/api/#jinja2.FunctionLoader
  124. """
  125. def loader(self, template_id):
  126. tpl = graph.get_template(template_id)
  127. return tpl['content'] if 'content' in tpl else ''
  128. # This is used for rendering articles templates. We keep this as a separate
  129. # function in order to avoid mixing articles templates with app templates.
  130. article_template = functools.partial(
  131. bottle.template,
  132. template_adapter = NodeTemplate,
  133. template_lookup = [],
  134. template_settings = {
  135. 'filters': {
  136. 'clear_url': clear_url,
  137. 'simple_date': lambda date: dateutil.parser.parse(date).strftime('%H:%M, %d %b %Y'),
  138. 'template': lambda string, **parameters: article_template(string+'\n', **parameters)
  139. },
  140. 'globals': {
  141. # deprecated
  142. 'archive': read_archive,
  143. 'blob': read_archive,
  144. 'dump': dump,
  145. 'query': query,
  146. },
  147. 'autoescape': False
  148. })
  149. def page_type_filter(config):
  150. """
  151. A type filter used for routes.
  152. Matches valid page types for the editor.
  153. Example. @get('/dokk/<type:page>')
  154. """
  155. # article Free-text input for a node. Used for describing a node.
  156. # file Structured data for describing a file.
  157. # query A query template. Queries can be used inside articles.
  158. # template A text template. Templates can be used inside articles.
  159. # topic Structured data for a node. Complements "article".
  160. regexp = '(article|file|query|template|topic)'
  161. def to_python(match):
  162. return match
  163. def to_url(page):
  164. return page
  165. return regexp, to_python, to_url
  166. # Bind the page-type filter
  167. application.router.add_filter('page', page_type_filter)
  168. # Load application routes
  169. import dokk.routes
  170. # Load custom error pages
  171. import dokk.error