jinja 作为在 python 中非常流行的模板语言,具有非常多好的特性,例如流程控制,继承,替换块…。此外 jinja 还支持丰富的扩展。jinja 目前有两个版本,分别是 jinja1 和 jinja2,这两个版本是不兼容的,本文讲述的是 jinja2。而且,需要说明的是,本文主要讲述的是 jinja2 的执行环境等信息,而不讲述太多 jinja2 的语法。
Jinja2 内部实现原生使用 Unicode,也就是说在渲染模板的时候你只能使用 Unicode 对象或者只包含 ASCII字符 的字节串。换行的话默认是 UNIX 风格的换行,也就是 “\n”。
首先,先展示一下在 python 中使用 jinja2 的示例:
语法
一个简单的示例
[root@liqiang.io]# cat test.py
from jinja2 import Template
data = {
"name": "Liqiang Liu",
"domain": "liqiang.io",
}
template = Template('Hello {{name}}!')
template.render(data)
Block
[root@liqiang.io]# cat test.py
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
条件控制
[root@liqiang.io]# cat test.py
{% if user %}
{{ user }}
{% else %}
hello!
{% for index in indexs %}
{{ index }}
{% endfor %}
Filter 过滤器
[root@liqiang.io]# cat test.py
{{ "hello world" | reverse | upper }}
{% filter upper %}
content passed to the filter
{% endfilter %}
在我们日常的使用中,更多的使用场景是作为 web 页面模板,或者一些文件的模板。如果说我们是将文件读出来,然后再通过这个例子渲染,这样就有点麻烦了,不 pythonic。
所以在 jinja2 的使用中,我关注两个点:
- 模板从哪个位置找
- 模板里面的数据(变量/过滤器/测试函数…)怎么传递
Environment
Enviroment 顾名思义就是环境的意思,而这个类的实例被存储在 configuration 和 global
对象上,当我们要加载模板文件的时候,就是使用这个类用以加载的,无论你是从文件系统还是其他地方,都是使用这个类。
Example
[root@liqiang.io]# cat test.py
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 对象。
[root@liqiang.io]# cat test.py
template = env.get_template('mytemplate.html')
Loaders
至此,我们还是不知道模板的位置在哪里,但是,有一个疑惑的地方没有说明就是创建 Environment 变量的时候,我们传了一个参数,这个参数叫做 loader,没有说明是用来干嘛的。其实,loader 就是我们加载模板的关键,Loader 的职责就是从资源(eg. 模板系统)中将模板加载进来。
Environment 会将渲染后的模板保留在内存中,但是不是无限时间得保留,而是在默认的一个时间内失效,然后重新渲染一份新的。所有的 Loader 都是 BaseLoader 的子类,如果你希望创建自己的 Loader,那么你需要继承 BaseLoader 并重写 get_source 函数。
Example
[root@liqiang.io]# cat test.py
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
[root@liqiang.io]# cat test.py
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
FileSystemLoader
class jinja2.FileSystemLoader(searchpath, encoding='utf-8', followlinks=False)
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.PackageLoader
class jinja2.PackageLoader(package_name, package_path='templates', encoding='utf-8')
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:DictLoader
class jinja2.DictLoader(mapping)
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:FunctionLoader
class jinja2.FunctionLoader(load_func)
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.
PrefixLoader
class jinja2.PrefixLoader(mapping, delimiter='/')
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:
ChoiceLoader
class jinja2.ChoiceLoader(loaders)
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.
ModuleLoader
class jinja2.ModuleLoader(path)
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 并作为第一个参数传递,但是,在函数内部只能执行读的操作。
Filter
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]