从 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,并且改动程度并不是很大。

posted @ 2021-01-23 13:57  Veoco  阅读(276)  评论(0编辑  收藏  举报