blob: 96e6f667cc9e1e122662bdd8f92f066c72cfa985 [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
webapp2_extras.jinja2
=====================
Jinja2 template support for webapp2.
Learn more about Jinja2: http://jinja.pocoo.org/
:copyright: 2011 by tipfy.org.
:license: Apache Sotware License, see LICENSE for details.
"""
from __future__ import absolute_import
import jinja2
import webapp2
#: Default configuration values for this module. Keys are:
#:
#: template_path
#: Directory for templates. Default is `templates`.
#:
#: compiled_path
#: Target for compiled templates. If set, uses the loader for compiled
#: templates in production. If it ends with a '.zip' it will be treated
#: as a zip file. Default is None.
#:
#: force_compiled
#: Forces the use of compiled templates even in the development server.
#:
#: environment_args
#: Keyword arguments used to instantiate the Jinja2 environment. By
#: default autoescaping is enabled and two extensions are set:
#: ``jinja2.ext.autoescape`` and ``jinja2.ext.with_``. For production it
#: may be a good idea to set 'auto_reload' to False -- we don't need to
#: check if templates changed after deployed.
#:
#: globals
#: Extra global variables for the Jinja2 environment.
#:
#: filters
#: Extra filters for the Jinja2 environment.
default_config = {
'template_path': 'templates',
'compiled_path': None,
'force_compiled': False,
'environment_args': {
'autoescape': True,
'extensions': [
'jinja2.ext.autoescape',
'jinja2.ext.with_',
],
},
'globals': None,
'filters': None,
}
class Jinja2(object):
"""Wrapper for configurable and cached Jinja2 environment.
To used it, set it as a cached property in a base `RequestHandler`::
import webapp2
from webapp2_extras import jinja2
class BaseHandler(webapp2.RequestHandler):
@webapp2.cached_property
def jinja2(self):
# Returns a Jinja2 renderer cached in the app registry.
return jinja2.get_jinja2(app=self.app)
def render_response(self, _template, **context):
# Renders a template and writes the result to the response.
rv = self.jinja2.render_template(_template, **context)
self.response.write(rv)
Then extended handlers can render templates directly::
class MyHandler(BaseHandler):
def get(self):
context = {'message': 'Hello, world!'}
self.render_response('my_template.html', **context)
"""
#: Configuration key.
config_key = __name__
#: Loaded configuration.
config = None
def __init__(self, app, config=None):
"""Initializes the Jinja2 object.
:param app:
A :class:`webapp2.WSGIApplication` instance.
:param config:
A dictionary of configuration values to be overridden. See
the available keys in :data:`default_config`.
"""
self.config = config = app.config.load_config(self.config_key,
default_values=default_config, user_values=config,
required_keys=None)
kwargs = config['environment_args'].copy()
enable_i18n = 'jinja2.ext.i18n' in kwargs.get('extensions', [])
if 'loader' not in kwargs:
template_path = config['template_path']
compiled_path = config['compiled_path']
use_compiled = not app.debug or config['force_compiled']
if compiled_path and use_compiled:
# Use precompiled templates loaded from a module or zip.
kwargs['loader'] = jinja2.ModuleLoader(compiled_path)
else:
# Parse templates for every new environment instances.
kwargs['loader'] = jinja2.FileSystemLoader(template_path)
# Initialize the environment.
env = jinja2.Environment(**kwargs)
if config['globals']:
env.globals.update(config['globals'])
if config['filters']:
env.filters.update(config['filters'])
if enable_i18n:
# Install i18n.
from webapp2_extras import i18n
env.install_gettext_callables(
lambda x: i18n.gettext(x),
lambda s, p, n: i18n.ngettext(s, p, n),
newstyle=True)
env.filters.update({
'format_date': i18n.format_date,
'format_time': i18n.format_time,
'format_datetime': i18n.format_datetime,
'format_timedelta': i18n.format_timedelta,
})
self.environment = env
def render_template(self, _filename, **context):
"""Renders a template and returns a response object.
:param _filename:
The template filename, related to the templates directory.
:param context:
Keyword arguments used as variables in the rendered template.
These will override values set in the request context.
:returns:
A rendered template.
"""
return self.environment.get_template(_filename).render(**context)
def get_template_attribute(self, filename, attribute):
"""Loads a macro (or variable) a template exports. This can be used to
invoke a macro from within Python code. If you for example have a
template named `_foo.html` with the following contents:
.. sourcecode:: html+jinja
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
You can access this from Python code like this::
hello = get_template_attribute('_foo.html', 'hello')
return hello('World')
This function comes from `Flask`.
:param filename:
The template filename.
:param attribute:
The name of the variable of macro to acccess.
"""
template = self.environment.get_template(filename)
return getattr(template.module, attribute)
# Factories -------------------------------------------------------------------
#: Key used to store :class:`Jinja2` in the app registry.
_registry_key = 'webapp2_extras.jinja2.Jinja2'
def get_jinja2(factory=Jinja2, key=_registry_key, app=None):
"""Returns an instance of :class:`Jinja2` from the app registry.
It'll try to get it from the current app registry, and if it is not
registered it'll be instantiated and registered. A second call to this
function will return the same instance.
:param factory:
The callable used to build and register the instance if it is not yet
registered. The default is the class :class:`Jinja2` itself.
:param key:
The key used to store the instance in the registry. A default is used
if it is not set.
:param app:
A :class:`webapp2.WSGIApplication` instance used to store the instance.
The active app is used if it is not set.
"""
app = app or webapp2.get_app()
jinja2 = app.registry.get(key)
if not jinja2:
jinja2 = app.registry[key] = factory(app)
return jinja2
def set_jinja2(jinja2, key=_registry_key, app=None):
"""Sets an instance of :class:`Jinja2` in the app registry.
:param store:
An instance of :class:`Jinja2`.
:param key:
The key used to retrieve the instance from the registry. A default
is used if it is not set.
:param request:
A :class:`webapp2.WSGIApplication` instance used to retrieve the
instance. The active app is used if it is not set.
"""
app = app or webapp2.get_app()
app.registry[key] = jinja2