Use the controller's symbolic/lookup name as part of the key for context hooks
[mediagoblin.git] / mediagoblin / tools / template.py
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
17
18 import jinja2
19 from jinja2.ext import Extension
20 from jinja2.nodes import Include, Const
21
22 from babel.localedata import exists
23 from werkzeug.urls import url_quote_plus
24
25 from mediagoblin import mg_globals
26 from mediagoblin import messages
27 from mediagoblin import _version
28 from mediagoblin.tools import common
29 from mediagoblin.tools.translate import set_thread_locale
30 from mediagoblin.tools.pluginapi import (
31 get_hook_templates, hook_transform)
32 from mediagoblin.tools.timesince import timesince
33 from mediagoblin.meddleware.csrf import render_csrf_form_token
34
35
36
37 SETUP_JINJA_ENVS = {}
38
39
40 def get_jinja_env(template_loader, locale):
41 """
42 Set up the Jinja environment,
43
44 (In the future we may have another system for providing theming;
45 for now this is good enough.)
46 """
47 set_thread_locale(locale)
48
49 # If we have a jinja environment set up with this locale, just
50 # return that one.
51 if locale in SETUP_JINJA_ENVS:
52 return SETUP_JINJA_ENVS[locale]
53
54 # jinja2.StrictUndefined will give exceptions on references
55 # to undefined/unknown variables in templates.
56 template_env = jinja2.Environment(
57 loader=template_loader, autoescape=True,
58 undefined=jinja2.StrictUndefined,
59 extensions=[
60 'jinja2.ext.i18n', 'jinja2.ext.autoescape',
61 TemplateHookExtension])
62
63 template_env.install_gettext_callables(
64 mg_globals.thread_scope.translations.ugettext,
65 mg_globals.thread_scope.translations.ungettext)
66
67 # All templates will know how to ...
68 # ... fetch all waiting messages and remove them from the queue
69 # ... construct a grid of thumbnails or other media
70 # ... have access to the global and app config
71 template_env.globals['fetch_messages'] = messages.fetch_messages
72 template_env.globals['app_config'] = mg_globals.app_config
73 template_env.globals['global_config'] = mg_globals.global_config
74 template_env.globals['version'] = _version.__version__
75
76 template_env.filters['urlencode'] = url_quote_plus
77
78 # add human readable fuzzy date time
79 template_env.globals['timesince'] = timesince
80
81 # allow for hooking up plugin templates
82 template_env.globals['get_hook_templates'] = get_hook_templates
83
84 if exists(locale):
85 SETUP_JINJA_ENVS[locale] = template_env
86
87 return template_env
88
89
90 # We'll store context information here when doing unit tests
91 TEMPLATE_TEST_CONTEXT = {}
92
93
94 def render_template(request, template_path, context):
95 """
96 Render a template with context.
97
98 Always inserts the request into the context, so you don't have to.
99 Also stores the context if we're doing unit tests. Helpful!
100 """
101 template = request.template_env.get_template(
102 template_path)
103 context['request'] = request
104 rendered_csrf_token = render_csrf_form_token(request)
105 if rendered_csrf_token is not None:
106 context['csrf_token'] = render_csrf_form_token(request)
107
108 # allow plugins to do things to the context
109 context = hook_transform(
110 (request.controller_name, template_path),
111 context)
112
113 rendered = template.render(context)
114
115 if common.TESTS_ENABLED:
116 TEMPLATE_TEST_CONTEXT[template_path] = context
117
118 return rendered
119
120
121 def clear_test_template_context():
122 global TEMPLATE_TEST_CONTEXT
123 TEMPLATE_TEST_CONTEXT = {}
124
125
126 class TemplateHookExtension(Extension):
127 """
128 Easily loop through a bunch of templates from a template hook.
129
130 Use:
131 {% template_hook("comment_extras") %}
132
133 ... will include all templates hooked into the comment_extras section.
134 """
135
136 tags = set(["template_hook"])
137
138 def parse(self, parser):
139 includes = []
140 expr = parser.parse_expression()
141 lineno = expr.lineno
142 hook_name = expr.args[0].value
143
144 for template_name in get_hook_templates(hook_name):
145 includes.append(
146 parser.parse_import_context(
147 Include(Const(template_name), True, False, lineno=lineno),
148 True))
149
150 return includes