Django 的模板与 Jinja2

| 分类 programming  | 标签 python  Django  programming  Two Scoops of Django 

Django 1.8 支持很多模板引擎,但是内置的只有 DTL 和 Jinja2。

语法区别

不同的地方:

目的 DTL Jinja2
方法调用 {{ user.get_favorites }} {{ user.get_favorites() }}
过滤器参数 {{ toppings| join:”, “ }} {{ toppings|join(“, “) }}
空循环 {% empty %} {% else %}
循环变量 {{ forloop }} {{ loop }}
Cycle {% cycle “odd” “even” %} {% loop.cycle(“odd”, “even”) %}

相似的地方

目的 DTL Jinja2
条件 {% if topping==”sprinkles” %} {% if topping==”sprinkles” %}
条件 {% elif topping==”fudge” %} {% elif topping==”fudge” %}
条件 {% else %} {% else %}
is 操作符 {% customer is happy %} {% customer is happy %}

需要换到 Jinja2 吗?

实际上在同一个项目中可以同时使用这两个引擎。DTL 有大量的资源,而 Jinja2 性能更好。

DTL 优点

  • 文档齐全,例子丰富。
  • DTL+Django 的项目比 Jinja2+Django 的项目更成熟。
  • 大部分第三方应用都使用 DTL。
  • 将 DTL 转换成 Jinja2 要花大量时间。

Jinja2 的优点

  • 能独立于 Django 使用。
  • Jinja2 的语法更像 Python,因而更直观。
  • 更加显式,比如函数调用用括号。
  • 逻辑限制更少,可以处理更多逻辑。
  • 性能更好。

应该用哪个?

  • 新用户应该用 DTL。
  • 使用了 DTL 的老项目应该保持用 DTL,除了一些需要改善性能的页面。
  • 经验丰富的用户应该先尝试两者,再酌情选取。

选用 Jinja2 后的注意事项

CSRF 和 Jinja2

Jinja2 使用 Django CSRF 机制与 DTL 不同。使用如下代码将 CSRF 加入到 Jinja2 的模板中:


<div style="display:none;">
    <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
</div>

在 Jinja2 模板中使用 Tag

目前还不支持 Tag,不过可以:

在 Jinja2 模板中使用 Django 风格的模板过滤器

由于 Django 的默认模板过滤器就是函数,因此我们可以定制一个 Jinja2 环境,将它们包含进来:

# core/jinja2.py
from __future__ impor absolute_import  # Python 2 only


from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.urlresolvers import reverse
from django.template import defaultfilters

from jinja2 import Environment

def environment(**options):
	env = Environment(**options)
	env.globals.update({
		'static': staticfiles_storage.url,
		'url': reverse,
		'dj': defaultfilters
	})
	return env

上面的设置代码将所有的默认过滤器函数都放在了 dj 中。

以下是在 Jinja2 模板中将 Django 模板过滤器作为函数使用的例子:


<table><tbody>
{% for purchase in purchase_list %}
	<tr>
		<a href="{{ url("purchase:detail", pk=purchase.pk) }}">
			{{ purchase.title }}
		</a>
	</tr>
	<tr>{{ dj.date(purchase.created, "SHORT_DATE_FORMAT") }}</tr>
	<tr>{{ dj.floatformat(purchase.amount, 2) }}</tr>
{% endfor %}
</tbody></table>

如果不想用这种全局的方式,也可以用 Mixin 的方式,然后在模板中通过 view 变量来访问。

先创建一个 Mixin:

# core/mixins.py
from django.template import defaultfilters

class DjFilterMixin(object):
	dj = defaultfilters

然后,当视图继承了该 Mixin 后,就能在 Jinja2 模板中使用了:


<table><tbody>
{% for purchase in purchase_list %}
	<tr>
		<a href="{{ url("purchase:detail", pk=purchase.pk) }}">
			{{ purchase.title }}
		</a>
	</tr>
	<!-- Call the django.template.defaultfilters functions from the view -->
	<tr>{{ view.dj.date(purchase.created, "SHORT_DATE_FORMAT") }}</tr>
	<tr>{{ view.dj.floatformat(purchase.amount, 2) }}</tr>
{% endfor %}
</tbody></table>

Jinja2 模板不会调用 Context Processors 中的函数

context processors 是在 DTL 设置参数 settings.TEMPLATES 中的 context_processors 项中指定的一系列可调用对象,这些可调用对象接受一个 request 对象为入参,返回一个字典,字典的内容会合并到模板上下文对象中。

要在 Jinja2 中实现类似的功能,应将 context_processors 中的功能用 middleware 实现。

假设要实现在每个模板中都添加一个广告,原来 DTL 中用 context_processors 实现如下:

# advertisements/context_processors.py
import random

from advertisements.models import Advertisement as Ad

def advertisements(request):
	count = Advertisement.objects.filter(subject='ice-cream').count()
	ads = Advertisement.objects.filter(subject='ice-cream')
	return {'ad': ads[random.randrange(0, count)]}

然后在 base.html 中:


<!-- base.html -->
...
<div class="ice-cream-advertisement">
	<a href="{{ ad.url }}">
		<img src="{{ ad.image }}" />
	</a>
</div>
...

以上的代码不能在 Jinja2 模板中使用,但可以用 middleware 实现类似功能:

# advertisements/middleware.py
import random

from advertisements.models import Advertisement as Ad

def AdvertisementMiddleware(object):

	def process_request(request):
		count = Advertisement.objects.filter(subject='ice-cream').count()
		ads = Advertisement.objects.filter(subject='ice-cream')
		# If necessary, add a context variable to the request object.
		if not hasattr(request, 'context'):
			request.context = {}
		# Don't overwrite the context, instead we build on it.
		request.context.update({'ad': ads[random.randrange(0, count)]})

然后在 Jinja2 模板中使用:


<!-- base.html -->
{% set ctx = request.context %}
...
<div class="ice-cream-advertisement">
	<a href="{{ ctx.ad.url }}">
		<img src="ctx.ad.image.url" />
	</a>
</div>
...

Jinja2 的 Environment 对象应该认为是静态的

Jinja2 通过 jinja2.Environment 类的实例在模板间共享配置信息、过滤器、全局变量等。当第一个模板加载时,Jinja2 会对它进行初始化,然后保持不变,因而它本质上是一个 static object

参考文献: Two Scoops of Django: Best Practices for Django 1.8


上一篇     下一篇