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