template.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import six
  17. import jinja2
  18. from jinja2.ext import Extension
  19. from jinja2.nodes import Include, Const
  20. from babel.localedata import exists
  21. from werkzeug.urls import url_quote_plus
  22. from mediagoblin import mg_globals
  23. from mediagoblin import messages
  24. from mediagoblin import _version
  25. from mediagoblin.tools import common
  26. from mediagoblin.tools.translate import is_rtl
  27. from mediagoblin.tools.translate import set_thread_locale
  28. from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
  29. from mediagoblin.tools.timesince import timesince
  30. from mediagoblin.meddleware.csrf import render_csrf_form_token
  31. SETUP_JINJA_ENVS = {}
  32. def get_jinja_env(app, template_loader, locale):
  33. """
  34. Set up the Jinja environment,
  35. (In the future we may have another system for providing theming;
  36. for now this is good enough.)
  37. """
  38. set_thread_locale(locale)
  39. # If we have a jinja environment set up with this locale, just
  40. # return that one.
  41. if locale in SETUP_JINJA_ENVS:
  42. return SETUP_JINJA_ENVS[locale]
  43. # The default config does not require a [jinja2] block.
  44. # You may create one if you wish to enable additional jinja2 extensions,
  45. # see example in config_spec.ini
  46. jinja2_config = app.global_config.get('jinja2', {})
  47. local_exts = jinja2_config.get('extensions', [])
  48. # jinja2.StrictUndefined will give exceptions on references
  49. # to undefined/unknown variables in templates.
  50. template_env = jinja2.Environment(
  51. loader=template_loader, autoescape=True,
  52. undefined=jinja2.StrictUndefined,
  53. extensions=[
  54. 'jinja2.ext.i18n', 'jinja2.ext.autoescape',
  55. TemplateHookExtension] + local_exts)
  56. if six.PY2:
  57. template_env.install_gettext_callables(mg_globals.thread_scope.translations.ugettext,
  58. mg_globals.thread_scope.translations.ungettext)
  59. else:
  60. template_env.install_gettext_callables(mg_globals.thread_scope.translations.gettext,
  61. mg_globals.thread_scope.translations.ngettext)
  62. # All templates will know how to ...
  63. # ... fetch all waiting messages and remove them from the queue
  64. # ... construct a grid of thumbnails or other media
  65. # ... have access to the global and app config
  66. # ... determine if the language is rtl or ltr
  67. template_env.globals['fetch_messages'] = messages.fetch_messages
  68. template_env.globals['app_config'] = app.app_config
  69. template_env.globals['global_config'] = app.global_config
  70. template_env.globals['version'] = _version.__version__
  71. template_env.globals['auth'] = app.auth
  72. template_env.globals['is_rtl'] = is_rtl(locale)
  73. template_env.filters['urlencode'] = url_quote_plus
  74. # add human readable fuzzy date time
  75. template_env.globals['timesince'] = timesince
  76. # allow for hooking up plugin templates
  77. template_env.globals['get_hook_templates'] = get_hook_templates
  78. template_env.globals = hook_transform(
  79. 'template_global_context', template_env.globals)
  80. #### THIS IS TEMPORARY, PLEASE FIX IT
  81. ## Notifications stuff is not yet a plugin (and we're not sure it will be),
  82. ## but it needs to add stuff to the context. This is THE WRONG WAY TO DO IT
  83. from mediagoblin import notifications
  84. template_env.globals['get_notifications'] = notifications.get_notifications
  85. template_env.globals[
  86. 'get_notification_count'] = notifications.get_notification_count
  87. template_env.globals[
  88. 'get_comment_subscription'] = notifications.get_comment_subscription
  89. if exists(locale):
  90. SETUP_JINJA_ENVS[locale] = template_env
  91. return template_env
  92. # We'll store context information here when doing unit tests
  93. TEMPLATE_TEST_CONTEXT = {}
  94. def render_template(request, template_path, context):
  95. """
  96. Render a template with context.
  97. Always inserts the request into the context, so you don't have to.
  98. Also stores the context if we're doing unit tests. Helpful!
  99. """
  100. template = request.template_env.get_template(
  101. template_path)
  102. context['request'] = request
  103. rendered_csrf_token = render_csrf_form_token(request)
  104. if rendered_csrf_token is not None:
  105. context['csrf_token'] = render_csrf_form_token(request)
  106. # allow plugins to do things to the context
  107. if request.controller_name:
  108. context = hook_transform(
  109. (request.controller_name, template_path),
  110. context)
  111. # More evil: allow plugins to possibly do something to the context
  112. # in every request ever with access to the request and other
  113. # variables. Note: this is slower than using
  114. # template_global_context
  115. context = hook_transform(
  116. 'template_context_prerender', context)
  117. rendered = template.render(context)
  118. if common.TESTS_ENABLED:
  119. TEMPLATE_TEST_CONTEXT[template_path] = context
  120. return rendered
  121. def clear_test_template_context():
  122. global TEMPLATE_TEST_CONTEXT
  123. TEMPLATE_TEST_CONTEXT = {}
  124. class TemplateHookExtension(Extension):
  125. """
  126. Easily loop through a bunch of templates from a template hook.
  127. Use:
  128. {% template_hook("comment_extras") %}
  129. ... will include all templates hooked into the comment_extras section.
  130. """
  131. tags = set(["template_hook"])
  132. def parse(self, parser):
  133. includes = []
  134. expr = parser.parse_expression()
  135. lineno = expr.lineno
  136. hook_name = expr.args[0].value
  137. for template_name in get_hook_templates(hook_name):
  138. includes.append(
  139. parser.parse_import_context(
  140. Include(Const(template_name), True, False, lineno=lineno),
  141. True))
  142. return includes