从 Jinja2 迁回到 Django 模板系统(DTL)
许多教程都推荐使用 Jinja2 来代替 Django 自带的模板系统(DTL),主要原因是 Jinja2 的通用性和 DTL 早期可能存在的性能问题。通用性指你可以在 Flask 等其他框架使用 Jinja2,但无法使用 DTL。而性能问题指的是 DTL 早期版本没有缓存(?)。
从我个人的使用体验来说,最大的感觉是 Jinja2 的自由度更高,你可以很自然的在模板中运行常用的 Python 代码。而 DTL 不一样,Django 的设计团队有意限制 DTL 的灵活性,他们认为 “模板系统是为了表达表现形式,而不是程序逻辑。”,因此提倡将复杂的逻辑放在视图中处理,保持模板的简单。
话说回来,几乎没有理由在已经选择了 Jinja2 之后再将其转回 DTL,绝大多数功能都可以在 Jinja2 上完美使用。但是,如果要增加国际化相关的工具就很麻烦,Jinja2 又没有官方中文文档(而 Django 有),因此为了方便的使用国际化,还是选择转回 DTL。有良好的中文文档,且速度估计也不会有多少差距。
上下文处理器
我的项目有一些全局变量想在整个模板系统共享,而不是在每个视图中手动加入。在 Jinja2 中,我可以直接将其在初始化环境变量的时候加进去。但是 DTL 并没有环境变量,因此需要自己手动增加一个上下文处理器,将全局变量添加到上下文中。首先创建 myproject/myproject/context_processors.py
:
def add_env(request):
return {'my': 'myproject'}
上下文处理器就是一个接收 request
参数并返回一个字典的函数,返回的字典会自动添加到上下文中。然后在 settings.py
中加入整个上下文处理器:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'myproject.context_processors.add_env',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
}
]
这样,我们在使用模板的时候就可以直接使用 {{ my }}
变量而无需任何其他导入语句。
变量计算与过滤器
正如之前所说的,Django 设计团队希望将复杂逻辑放在视图中,但也并非没有变通的方法。下面有几个我遇到的问题,基本都可以通过自带的标签和手动编写自定义过滤器解决。
数学运算
DTL 中没有加减乘除这样的数学运算语法,因此对于加减我们只能使用 add
过滤器来解决:
{{ a|add:10 }}
{{ a|add:b }}
如果要运算后保存为变量,需要使用 with
标签:
{{ with res=a|add:10 }}
{% endwith %}
对于乘除法就需要 widthratio
,同时使用 as
语法保存为变量:
{% widthratio a 10 1 as res %}
{% widthratio a max_value value as res %}
res
的结果为 a * value / max_value
,因此第一个 res
结果为 a
的 0.1 倍。
过滤器
从 Jinja2 转过来会发现一个问题,DTL 过滤器除了值本身只能传入一个参数。为了规避这个问题,只能把原有的多参数函数包装起来,用多个函数来调用原有的函数。如下示例,原本只需再传入一个 img=True
参数即可让函数输出 HTML,但是如今只能传入一个参数,只能再写一个函数来调用了:
@register.filter
def gravatar(email, size=40):
md5 = hashlib.md5(email.lower().encode('utf-8')).hexdigest()
url = 'https://www.gravatar.com/avatar/{0}?d=retro&s={1}'.format(md5, size)
return url
@register.filter
def gravatar_img(email, size=40):
url = gravatar(email, size)
return mark_safe('<img src="{0}" width="{1}" height="{1}">'.format(url, size))
此外,还遇到 Jinja2 的 tojson
过滤器在 DTL 不存在,只能用自定义过滤器替代。通过自定义过滤器基本所有 Jinja2 有的功能都可以替代。
总结
Jinja2 灵活度比 DTL 高,但是从我的实践上来说 Jinja2 完全可以迁回到 DTL,并且改动程度并不是很大。