this negation needs parens.
[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 is_rtl
30 from mediagoblin.tools.translate import set_thread_locale
31 from mediagoblin.tools.translate import get_locale_from_request
32 from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
33 from mediagoblin.tools.timesince import timesince
34 from mediagoblin.meddleware.csrf import render_csrf_form_token
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 # The default config does not require a [jinja2] block.
55 # You may create one if you wish to enable additional jinja2 extensions,
56 # see example in config_spec.ini
57 jinja2_config = mg_globals.global_config.get('jinja2', {})
58 local_exts = jinja2_config.get('extensions', [])
59
60 # jinja2.StrictUndefined will give exceptions on references
61 # to undefined/unknown variables in templates.
62 template_env = jinja2.Environment(
63 loader=template_loader, autoescape=True,
64 undefined=jinja2.StrictUndefined,
65 extensions=[
66 'jinja2.ext.i18n', 'jinja2.ext.autoescape',
67 TemplateHookExtension] + local_exts)
68
69 template_env.install_gettext_callables(
70 mg_globals.thread_scope.translations.ugettext,
71 mg_globals.thread_scope.translations.ungettext)
72
73 # All templates will know how to ...
74 # ... fetch all waiting messages and remove them from the queue
75 # ... construct a grid of thumbnails or other media
76 # ... have access to the global and app config
77 # ... determine if the language is rtl or ltr
78 template_env.globals['fetch_messages'] = messages.fetch_messages
79 template_env.globals['app_config'] = mg_globals.app_config
80 template_env.globals['global_config'] = mg_globals.global_config
81 template_env.globals['version'] = _version.__version__
82 template_env.globals['auth'] = mg_globals.app.auth
83 template_env.globals['is_rtl'] = is_rtl(locale)
84 template_env.filters['urlencode'] = url_quote_plus
85
86 # add human readable fuzzy date time
87 template_env.globals['timesince'] = timesince
88
89 # allow for hooking up plugin templates
90 template_env.globals['get_hook_templates'] = get_hook_templates
91
92 template_env.globals = hook_transform(
93 'template_global_context', template_env.globals)
94
95 #### THIS IS TEMPORARY, PLEASE FIX IT
96 ## Notifications stuff is not yet a plugin (and we're not sure it will be),
97 ## but it needs to add stuff to the context. This is THE WRONG WAY TO DO IT
98 from mediagoblin import notifications
99 template_env.globals['get_notifications'] = notifications.get_notifications
100 template_env.globals[
101 'get_notification_count'] = notifications.get_notification_count
102 template_env.globals[
103 'get_comment_subscription'] = notifications.get_comment_subscription
104
105 if exists(locale):
106 SETUP_JINJA_ENVS[locale] = template_env
107
108 return template_env
109
110
111 # We'll store context information here when doing unit tests
112 TEMPLATE_TEST_CONTEXT = {}
113
114
115 def render_template(request, template_path, context):
116 """
117 Render a template with context.
118
119 Always inserts the request into the context, so you don't have to.
120 Also stores the context if we're doing unit tests. Helpful!
121 """
122 template = request.template_env.get_template(
123 template_path)
124 context['request'] = request
125 rendered_csrf_token = render_csrf_form_token(request)
126 if rendered_csrf_token is not None:
127 context['csrf_token'] = render_csrf_form_token(request)
128
129 # allow plugins to do things to the context
130 if request.controller_name:
131 context = hook_transform(
132 (request.controller_name, template_path),
133 context)
134
135 # More evil: allow plugins to possibly do something to the context
136 # in every request ever with access to the request and other
137 # variables. Note: this is slower than using
138 # template_global_context
139 context = hook_transform(
140 'template_context_prerender', context)
141
142 rendered = template.render(context)
143
144 if common.TESTS_ENABLED:
145 TEMPLATE_TEST_CONTEXT[template_path] = context
146
147 return rendered
148
149
150 def clear_test_template_context():
151 global TEMPLATE_TEST_CONTEXT
152 TEMPLATE_TEST_CONTEXT = {}
153
154
155 class TemplateHookExtension(Extension):
156 """
157 Easily loop through a bunch of templates from a template hook.
158
159 Use:
160 {% template_hook("comment_extras") %}
161
162 ... will include all templates hooked into the comment_extras section.
163 """
164
165 tags = set(["template_hook"])
166
167 def parse(self, parser):
168 includes = []
169 expr = parser.parse_expression()
170 lineno = expr.lineno
171 hook_name = expr.args[0].value
172
173 for template_name in get_hook_templates(hook_name):
174 includes.append(
175 parser.parse_import_context(
176 Include(Const(template_name), True, False, lineno=lineno),
177 True))
178
179 return includes