1# -*- coding: utf-8 -*-
2"""
3    webapp2_extras.jinja2
4    =====================
5
6    Jinja2 template support for webapp2.
7
8    Learn more about Jinja2: http://jinja.pocoo.org/
9
10    :copyright: 2011 by tipfy.org.
11    :license: Apache Sotware License, see LICENSE for details.
12"""
13from __future__ import absolute_import
14
15import jinja2
16
17import webapp2
18
19#: Default configuration values for this module. Keys are:
20#:
21#: template_path
22#:     Directory for templates. Default is `templates`.
23#:
24#: compiled_path
25#:     Target for compiled templates. If set, uses the loader for compiled
26#:     templates in production. If it ends with a '.zip' it will be treated
27#:     as a zip file. Default is None.
28#:
29#: force_compiled
30#:     Forces the use of compiled templates even in the development server.
31#:
32#: environment_args
33#:     Keyword arguments used to instantiate the Jinja2 environment. By
34#:     default autoescaping is enabled and two extensions are set:
35#:     ``jinja2.ext.autoescape`` and ``jinja2.ext.with_``. For production it
36#:     may be a good idea to set 'auto_reload' to False -- we don't need to
37#:     check if templates changed after deployed.
38#:
39#: globals
40#:     Extra global variables for the Jinja2 environment.
41#:
42#: filters
43#:     Extra filters for the Jinja2 environment.
44default_config = {
45    'template_path': 'templates',
46    'compiled_path': None,
47    'force_compiled': False,
48    'environment_args': {
49        'autoescape': True,
50        'extensions': [
51            'jinja2.ext.autoescape',
52            'jinja2.ext.with_',
53        ],
54    },
55    'globals': None,
56    'filters': None,
57}
58
59
60class Jinja2(object):
61    """Wrapper for configurable and cached Jinja2 environment.
62
63    To used it, set it as a cached property in a base `RequestHandler`::
64
65        import webapp2
66
67        from webapp2_extras import jinja2
68
69        class BaseHandler(webapp2.RequestHandler):
70
71            @webapp2.cached_property
72            def jinja2(self):
73                # Returns a Jinja2 renderer cached in the app registry.
74                return jinja2.get_jinja2(app=self.app)
75
76            def render_response(self, _template, **context):
77                # Renders a template and writes the result to the response.
78                rv = self.jinja2.render_template(_template, **context)
79                self.response.write(rv)
80
81    Then extended handlers can render templates directly::
82
83        class MyHandler(BaseHandler):
84            def get(self):
85                context = {'message': 'Hello, world!'}
86                self.render_response('my_template.html', **context)
87    """
88
89    #: Configuration key.
90    config_key = __name__
91
92    #: Loaded configuration.
93    config = None
94
95    def __init__(self, app, config=None):
96        """Initializes the Jinja2 object.
97
98        :param app:
99            A :class:`webapp2.WSGIApplication` instance.
100        :param config:
101            A dictionary of configuration values to be overridden. See
102            the available keys in :data:`default_config`.
103        """
104        self.config = config = app.config.load_config(self.config_key,
105            default_values=default_config, user_values=config,
106            required_keys=None)
107        kwargs = config['environment_args'].copy()
108        enable_i18n = 'jinja2.ext.i18n' in kwargs.get('extensions', [])
109
110        if 'loader' not in kwargs:
111            template_path = config['template_path']
112            compiled_path = config['compiled_path']
113            use_compiled = not app.debug or config['force_compiled']
114
115            if compiled_path and use_compiled:
116                # Use precompiled templates loaded from a module or zip.
117                kwargs['loader'] = jinja2.ModuleLoader(compiled_path)
118            else:
119                # Parse templates for every new environment instances.
120                kwargs['loader'] = jinja2.FileSystemLoader(template_path)
121
122        # Initialize the environment.
123        env = jinja2.Environment(**kwargs)
124
125        if config['globals']:
126            env.globals.update(config['globals'])
127
128        if config['filters']:
129            env.filters.update(config['filters'])
130
131        if enable_i18n:
132            # Install i18n.
133            from webapp2_extras import i18n
134            env.install_gettext_callables(
135                lambda x: i18n.gettext(x),
136                lambda s, p, n: i18n.ngettext(s, p, n),
137                newstyle=True)
138            env.filters.update({
139                'format_date':      i18n.format_date,
140                'format_time':      i18n.format_time,
141                'format_datetime':  i18n.format_datetime,
142                'format_timedelta': i18n.format_timedelta,
143            })
144
145        self.environment = env
146
147    def render_template(self, _filename, **context):
148        """Renders a template and returns a response object.
149
150        :param _filename:
151            The template filename, related to the templates directory.
152        :param context:
153            Keyword arguments used as variables in the rendered template.
154            These will override values set in the request context.
155        :returns:
156            A rendered template.
157        """
158        return self.environment.get_template(_filename).render(**context)
159
160    def get_template_attribute(self, filename, attribute):
161        """Loads a macro (or variable) a template exports.  This can be used to
162        invoke a macro from within Python code.  If you for example have a
163        template named `_foo.html` with the following contents:
164
165        .. sourcecode:: html+jinja
166
167           {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
168
169        You can access this from Python code like this::
170
171            hello = get_template_attribute('_foo.html', 'hello')
172            return hello('World')
173
174        This function comes from `Flask`.
175
176        :param filename:
177            The template filename.
178        :param attribute:
179            The name of the variable of macro to acccess.
180        """
181        template = self.environment.get_template(filename)
182        return getattr(template.module, attribute)
183
184
185# Factories -------------------------------------------------------------------
186
187
188#: Key used to store :class:`Jinja2` in the app registry.
189_registry_key = 'webapp2_extras.jinja2.Jinja2'
190
191
192def get_jinja2(factory=Jinja2, key=_registry_key, app=None):
193    """Returns an instance of :class:`Jinja2` from the app registry.
194
195    It'll try to get it from the current app registry, and if it is not
196    registered it'll be instantiated and registered. A second call to this
197    function will return the same instance.
198
199    :param factory:
200        The callable used to build and register the instance if it is not yet
201        registered. The default is the class :class:`Jinja2` itself.
202    :param key:
203        The key used to store the instance in the registry. A default is used
204        if it is not set.
205    :param app:
206        A :class:`webapp2.WSGIApplication` instance used to store the instance.
207        The active app is used if it is not set.
208    """
209    app = app or webapp2.get_app()
210    jinja2 = app.registry.get(key)
211    if not jinja2:
212        jinja2 = app.registry[key] = factory(app)
213
214    return jinja2
215
216
217def set_jinja2(jinja2, key=_registry_key, app=None):
218    """Sets an instance of :class:`Jinja2` in the app registry.
219
220    :param store:
221        An instance of :class:`Jinja2`.
222    :param key:
223        The key used to retrieve the instance from the registry. A default
224        is used if it is not set.
225    :param request:
226        A :class:`webapp2.WSGIApplication` instance used to retrieve the
227        instance. The active app is used if it is not set.
228    """
229    app = app or webapp2.get_app()
230    app.registry[key] = jinja2
231