jinja 作为在 python 中非常流行的模板语言,具有非常多好的特性,例如流程控制,继承,替换块...。此外 jinja 还支持丰富的扩展。jinja 目前有两个版本,分别是 jinja1 和 jinja2,这两个版本是不兼容的,本文讲述的是 jinja2。而且,需要说明的是,本文主要讲述的是 jinja2 的执行环境等信息,而不讲述太多 jinja2 的语法。


Unicode

Jinja2 内部实现原生使用 Unicode,也就是说在渲染模板的时候你只能使用 Unicode 对象或者只包含 ASCII字符 的字节串。换行的话默认是 UNIX风格的换行,也就是 "\n"。

首先,先展示一下在 python 中使用 jinja2 的示例:

Simple Example

from jinja2 import Template

template = Template('Hello {{name}}!')
template.render(name="Tyrael")


这是一个简单的从字符串渲染模板的例子,然而,在我们日常的使用中,更多的使用场景是作为 web页面模板,或者一些文件的模板。如果说我们是将文件读出来,然后再通过这个例子渲染,这样就有点麻烦了,不 pythonic。

所以在 jinja2 的使用中,我关注两个点:

Environment

Enviroment 顾名思义就是环境的意思,而这个类的实例被存储在 configurationglobal 对象上,当我们要加载模板文件的时候,就是使用这个类用以加载的,无论你是从文件系统还是其他地方,都是使用这个类。

Example

from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('yourapplication', 'templates'))


这是创建一个简单的从文件系统中加载模板文件的 Environment 的例子。

Different loaders are available and you can also write your own if you want to load templates from a database or other resources.

load template

下一步,需要探索的就是如何使用 Enviroment 加载模板文件,上面只是创建了一个 Environment 变量;如果你想要使用 Environment 对象加载一个模板,你只需要调用 get_template 函数,如果模板存在的话,它将返回一个 Template 对象。

template = env.get_template('mytemplate.html')


Loaders

至此,我们还是不知道模板的位置在哪里,但是,有一个疑惑的地方没有说明就是创建 Environment 变量的时候,我们传了一个参数,这个参数叫做 loader,没有说明是用来干嘛的。其实,loader 就是我们加载模板的关键,Loader 的职责就是从资源(eg. 模板系统)中将模板加载进来。

Environment 会将渲染后的模板保留在内存中,但是不是无限时间得保留,而是在默认的一个时间内失效,然后重新渲染一份新的。所有的 Loader 都是 BaseLoader 的子类,如果你希望创建自己的 Loader,那么你需要继承 BaseLoader 并重写 get_source 函数。

Example

from jinja2 import BaseLoader, TemplateNotFound
from os.path import join, exists, getmtime

class MyLoader(BaseLoader):

    def __init__(self, path):
        self.path = path

    def get_source(self, environment, template):
        path = join(self.path, template)
        if not exists(path):
            raise TemplateNotFound(template)
        mtime = getmtime(path)
        with file(path) as f:
            source = f.read().decode('utf-8')
        return source, path, lambda: mtime == getmtime(path)


load function

load(environment, name, globals=None)


Loads a template. This method looks up the template in the cache or loads one by calling get_source(). Subclasses should not override this method as loaders working on collections of other loaders (such as PrefixLoader or ChoiceLoader) will not call this method but get_source directly.

builtin loaders Jinja2 provides

Loads templates from the file system. This loader can find templates in folders on the file system and is the preferred way to load them.

Load templates from python eggs or packages. It is constructed with the name of the python package and the path to the templates in that package:

Loads a template from a python dict. It's passed a dict of unicode strings bound to template names. This loader is useful for unittesting:

A loader that is passed a function which does the loading. The function receives the name of the template and has to return either an unicode string with the template source, a tuple in the form (source, filename, uptodatefunc) or None if the template does not exist.

A loader that is passed a dict of loaders where each loader is bound to a prefix. The prefix is delimited from the template by a slash per default, which can be changed by setting the delimiter argument to something else:

This loader works like the PrefixLoader just that no prefix is specified. If a template could not be found by one loader the next one is tried.

This loader loads templates from precompiled templates.

Autoescaping

def guess_autoescape(template_name):
    if template_name is None or '.' not in template_name:
        return False
    ext = template_name.rsplit('.', 1)[1]
    return ext in ('html', 'htm', 'xml')

env = Environment(autoescape=guess_autoescape,
                  loader=PackageLoader('mypackage'),
                  extensions=['jinja2.ext.autoescape'])


Context

loader 解决了模板文件从哪来的问题,最后一个问题就是变量等从哪来。在 Jinja2 中,模板 Context 包含一个模板的所有变量,存储了所有传给模板的值以及模板暴露出来的名称。当渲染模板的时候,Jinja2 不仅支持创建 Context 实例,而且这是非常有用的,但是,作为使用者,我们不应该手动创建实例。

context 是不可更改的对象,在 parent 修改是不会生效的,如果在你渲染模板的额时候,你想要增加一个变量,那么可以使用 vars 。模板 filter 和全局函数被标记为 contextfunction() ,这个装饰器会获得可用的 context 并作为第一个参数传递,但是,在函数内部只能执行读的操作。

Custom Filter

def datetimeformat(value, format='%H:%M / %d-%m-%Y'):
    return value.strftime(format)


You can register it on the template environment by updating the filters dict on the environment:

environment.filters['datetimeformat'] = datetimeformat


Custom Tests

Tests work like filters just that there is no way for a test to get access to the environment or context and that they can't be chained. The return value of a test should be True or False. The purpose of a test is to give the template designers the possibility to perform type and conformability checks.

Example

import math

def is_prime(n):
    if n == 2:
        return True
    for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1):
        if n % i == 0:
            return False
    return True

environment.tests['prime'] = is_prime


template

{% if 42 is prime %}
    42 is a prime number
{% else %}
    42 is not a prime number
{% endif %}


Classes

Enviroment、Template、Loader、Context


Meta API

jinja2.meta.find_undeclared_variables(ast)


Returns a set of all variables in the AST that will be looked up from the context at runtime. Because at compile time it's not known which variables will be used depending on the path the execution takes at runtime, all variables are returned.

>>> from jinja2 import Environment, meta
>>> env = Environment()
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
>>> meta.find_undeclared_variables(ast) == set(['bar'])
True


#

jinja2.meta.find_referenced_templates(ast)


Finds all the referenced templates from the AST. This will return an iterator over all the hardcoded template extensions, inclusions and imports. If dynamic inheritance or inclusion is used, None will be yielded.

>>> from jinja2 import Environment, meta
>>> env = Environment()
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
>>> list(meta.find_referenced_templates(ast))
['layout.html', None]