普通文本  |  231行  |  7.52 KB

# -*- 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