Merge remote-tracking branch 'refs/remotes/tsyesika/master'
[mediagoblin.git] / mediagoblin / tools / template.py
CommitLineData
ae3bc7fa 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
ae3bc7fa
AW
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
927be5e8 17
ae3bc7fa 18import jinja2
927be5e8
CAW
19from jinja2.ext import Extension
20from jinja2.nodes import Include, Const
21
ae3bc7fa 22from babel.localedata import exists
620e4e1b
SS
23from werkzeug.urls import url_quote_plus
24
ae3bc7fa
AW
25from mediagoblin import mg_globals
26from mediagoblin import messages
2a0aed84 27from mediagoblin import _version
ae3bc7fa 28from mediagoblin.tools import common
50cb5122 29from mediagoblin.tools.translate import set_thread_locale
f7a5c7c7 30from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
f1c3807d 31from mediagoblin.tools.timesince import timesince
ce5ae8da 32from mediagoblin.meddleware.csrf import render_csrf_form_token
ae3bc7fa 33
ee91c2b8 34
ae3bc7fa
AW
35SETUP_JINJA_ENVS = {}
36
ee91c2b8 37
ae3bc7fa
AW
38def get_jinja_env(template_loader, locale):
39 """
ee91c2b8 40 Set up the Jinja environment,
ae3bc7fa
AW
41
42 (In the future we may have another system for providing theming;
43 for now this is good enough.)
44 """
50cb5122 45 set_thread_locale(locale)
ae3bc7fa
AW
46
47 # If we have a jinja environment set up with this locale, just
48 # return that one.
04453ccf 49 if locale in SETUP_JINJA_ENVS:
ae3bc7fa
AW
50 return SETUP_JINJA_ENVS[locale]
51
155d234d
KD
52 # The default config does not require a [jinja2] block.
53 # You may create one if you wish to enable additional jinja2 extensions,
54 # see example in config_spec.ini
55 jinja2_config = mg_globals.global_config.get('jinja2', {})
56 local_exts = jinja2_config.get('extensions', [])
57
f1cdd278
E
58 # jinja2.StrictUndefined will give exceptions on references
59 # to undefined/unknown variables in templates.
ae3bc7fa
AW
60 template_env = jinja2.Environment(
61 loader=template_loader, autoescape=True,
f1cdd278 62 undefined=jinja2.StrictUndefined,
927be5e8
CAW
63 extensions=[
64 'jinja2.ext.i18n', 'jinja2.ext.autoescape',
155d234d 65 TemplateHookExtension] + local_exts)
ae3bc7fa
AW
66
67 template_env.install_gettext_callables(
c80982c7
JK
68 mg_globals.thread_scope.translations.ugettext,
69 mg_globals.thread_scope.translations.ungettext)
ae3bc7fa
AW
70
71 # All templates will know how to ...
72 # ... fetch all waiting messages and remove them from the queue
73 # ... construct a grid of thumbnails or other media
53bc3975 74 # ... have access to the global and app config
ae3bc7fa 75 template_env.globals['fetch_messages'] = messages.fetch_messages
6950c6c7
CAW
76 template_env.globals['app_config'] = mg_globals.app_config
77 template_env.globals['global_config'] = mg_globals.global_config
2a0aed84 78 template_env.globals['version'] = _version.__version__
744f1c83 79 template_env.globals['auth'] = mg_globals.app.auth
ae3bc7fa 80
620e4e1b
SS
81 template_env.filters['urlencode'] = url_quote_plus
82
f1c3807d
J
83 # add human readable fuzzy date time
84 template_env.globals['timesince'] = timesince
85
a3f811a6
CAW
86 # allow for hooking up plugin templates
87 template_env.globals['get_hook_templates'] = get_hook_templates
88
f7a5c7c7
CAW
89 template_env.globals = hook_transform(
90 'template_global_context', template_env.globals)
91
7949d88a
CAW
92 #### THIS IS TEMPORARY, PLEASE FIX IT
93 ## Notifications stuff is not yet a plugin (and we're not sure it will be),
94 ## but it needs to add stuff to the context. This is THE WRONG WAY TO DO IT
95 from mediagoblin import notifications
96 template_env.globals['get_notifications'] = notifications.get_notifications
29cd702e
CAW
97 template_env.globals[
98 'get_notification_count'] = notifications.get_notification_count
7949d88a
CAW
99 template_env.globals[
100 'get_comment_subscription'] = notifications.get_comment_subscription
101
ae3bc7fa
AW
102 if exists(locale):
103 SETUP_JINJA_ENVS[locale] = template_env
104
105 return template_env
106
ee91c2b8 107
ae3bc7fa
AW
108# We'll store context information here when doing unit tests
109TEMPLATE_TEST_CONTEXT = {}
110
111
112def render_template(request, template_path, context):
113 """
114 Render a template with context.
115
116 Always inserts the request into the context, so you don't have to.
117 Also stores the context if we're doing unit tests. Helpful!
118 """
119 template = request.template_env.get_template(
120 template_path)
121 context['request'] = request
71c6c432
E
122 rendered_csrf_token = render_csrf_form_token(request)
123 if rendered_csrf_token is not None:
124 context['csrf_token'] = render_csrf_form_token(request)
38103094
CAW
125
126 # allow plugins to do things to the context
f7a5c7c7
CAW
127 if request.controller_name:
128 context = hook_transform(
129 (request.controller_name, template_path),
130 context)
38103094 131
1c6d2e87
CAW
132 # More evil: allow plugins to possibly do something to the context
133 # in every request ever with access to the request and other
134 # variables. Note: this is slower than using
135 # template_global_context
136 context = hook_transform(
137 'template_context_prerender', context)
138
ae3bc7fa 139 rendered = template.render(context)
ee91c2b8
CAW
140
141 if common.TESTS_ENABLED:
ae3bc7fa
AW
142 TEMPLATE_TEST_CONTEXT[template_path] = context
143
144 return rendered
145
146
147def clear_test_template_context():
148 global TEMPLATE_TEST_CONTEXT
149 TEMPLATE_TEST_CONTEXT = {}
927be5e8
CAW
150
151
152class TemplateHookExtension(Extension):
153 """
154 Easily loop through a bunch of templates from a template hook.
155
156 Use:
157 {% template_hook("comment_extras") %}
158
159 ... will include all templates hooked into the comment_extras section.
160 """
161
8a4d0dbc 162 tags = set(["template_hook"])
927be5e8
CAW
163
164 def parse(self, parser):
165 includes = []
166 expr = parser.parse_expression()
167 lineno = expr.lineno
168 hook_name = expr.args[0].value
169
170 for template_name in get_hook_templates(hook_name):
171 includes.append(
172 parser.parse_import_context(
173 Include(Const(template_name), True, False, lineno=lineno),
174 True))
175
176 return includes