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