Django之模板层

作为一个Web框架,Django需要一种动态生成HTML的便捷方法。最常用的方法依赖于模板。模板包含所需HTML输出的静态部分以及描述动态内容将被插入的一些特殊语法。

Django之视图层中我们可以通过render方法,将数据渲染到指定的html模板中,本文介绍如何在模板中处理数据。

模板变量

在模板中输出变量的语法与vue一样使用双大括号:

{{var_name}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# views.py

def index(request):
import datetime
s = 'hello 中国'
l = ['中华', 666]
dic = {'name':'刘德华', 'age':18}
date = datetime.date(2020, 4, 17)
class Stu:
def __init__(self, name):
self.name = name
objs = [Stu('孙悟空'), Stu('猪八戒'), Stu('沙和尚')]

return render(request, 'index.html', {'s':s, 'l':l, 'dic':dic, 'date':date, 'objs':objs})

# index.html


<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>字符串:{% raw %}{{ s }}{% endraw %}</h2>
<h2>列表:{% raw %}{{ l.0 }}{% endraw %}</h2>
<h2>字典:{% raw %}{{ dic.name }}{% raw %}</h2>
<h2>日期:{% raw %}{{ date.year }}{% endraw %}</h2>
<h2>对象:{% raw %}{{ objs.0.name }}{% endraw %}</h2>

</body>
</html>

结果:

img

注意:句点符也可以用来引用对象的方法(无参数方法):

1
{% raw %}{{dic.name.upper}}{% endraw %}

模板过滤器

即在模板中使用函数,语法为:{{obj|filter__name:param}}

过滤器可以用来修改变量的显示样式。
过滤器的使用方式:{{变量|过滤器方法}} 。过滤器可以连续使用,形式如:{{变量|过滤器方法1|过滤器方法2}}
注意变量、管道符:(|)和过滤器方法之间不能有空格。
某些过滤器还可以接收参数,例如:{{titleltruncatewords:30}},这句代码的意思是显示title的前30 个单词。
如果过滤器参数包含空格的话,参数就要用引号包括,例如:{{list|join:","}}

下面介绍几个常用的过滤器:

default

如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。例如:

1
{% raw %}{{ value|default:"nothing"}}{% endraw %}

capfirst

首字母大写:{{valuelcapfirst}}

length

返回值的长度。它对字符串和列表都起作用。例如:

1
{% raw %}{{ value|length }}{% endraw %}

如果 value['a', 'b', 'c', 'd'],那么输出是 4。

filesizeformat

将值格式化为一个 “人类可读的” 文件尺寸 (例如 13 KB, 4.1 MB, 102 bytes, 等等)。例如:

1
{% raw %}{{ value|filesizeformat }}{% endraw %}

如果 value 是 123456789,输出将会是 117.7 MB。  

cut

删除指定值,例如去掉字符串中的空格:{{value|cut:""}}
如果value 是“ String with spaces ”,那么输出Stringwithspaces

date

如果 value=datetime.datetime.now()

1
{% raw %}{{ value|date:"Y-m-d"}}{% endraw %}

escape

将字符串进行HTML 转意,例如:

1
2
3
4
5
{% raw %}
{% autoescape off %}
{{ titlelescape }}
{% endautoescape %}
{% endraw %}

如果value 是“<Django>”,则输出<Django>

slice

如果 value="hello world"

1
{% raw %}{{ value|slice:"2:-1" }}{% endraw %}

filesizeformat

将文件大小按照人类可读的形式显示,例如一个文件有123456789 个字节,那么使用filesizeformat 将会显示成117 .7 MB ,语法形式:{{valuejfilesizeformat}}

truncatechars

如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“…”)结尾。

参数:要截断的字符数

例如:

1
{% raw %}{{ value|truncatechars:9 }}{% endraw %}

safe

Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。比如:

1
2
value="<a href=''>点击</a>"
{% raw %}{{ value|safe}}{% endraw %}

更多内置过滤器可参考官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#built-in-filter-reference

模板标签

标签看起来像是这样的: {% tag %}。标签比变量更加复杂:一些在输出中创建文本,一些通过循环或逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模版中。一些标签需要开始和结束标签 (例如{% tag %} ...标签 内容 … {% endtag %})。

for标签

遍历每一个元素:

1
2
3
4
5
{% raw %}
{% for person in person_list %}
<p>{{ person.name }}</p>
{% endfor %}
{% endraw %}

可以利用{% for obj in list reversed %}反向完成循环。

遍历一个字典:

1
2
3
4
5
{% raw %}
{% for key,val in dic.items %}
<p>{{ key }}:{{ val }}</p>
{% endfor %}
{% endraw %}

注:循环序号可以通过{{forloop}}{% endaw %}`显示  

1
2
3
4
5
6
forloop.counter            当前循环位置(以数字1位起始)
forloop.counter0 当前循环位置(以数字0位起始)
forloop.revcounter 反向循环位置(列表的最后一位是l ,列表第一位是n )
forloop.revcounter0 反向循环位置(列表的最后一位是0 , 列表第一位是n- 1 )
forloop.first 如果是当前循环的第一位,返回True
forloop.last 如果是当前循环的最后一位,返回True
#### `for ... empty` `for` 标签带有一个可选的`{% raw %}{% empty %}{% endraw %} 从句,以便在给出的组是空的或者没有被找到时,可以有所操作。

1
2
3
4
5
6
7
8
{% raw %}
{% for person in person_list %}
<p>{{ person.name }}</p>

{% empty %}
<p>sorry,no person here</p>
{% endfor %}
{% endraw %}

if 标签

{% raw %}{% if %}{% endraw %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。

1
2
3
4
5
6
7
8
9
{% raw %}
{% if num > 100 or num < 0 %}
<p>无效</p>
{% elif num > 80 and num < 100 %}
<p>优秀</p>
{% else %}
<p>凑活吧</p>
{% endif %}
{% endraw %}

with

使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的

例如:

1
2
3
4
5
{% raw %}
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
{% endraw %}

csrf_token

这个标签用于跨站请求伪造保护

更多内置标签参见官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#built-in-tag-reference

人性化语义标签

除了上述功能性标签外, Django 还提供了很多辅助性标签,这些标签只是为了使变量输
出变得更加可读,下面对这些标签进行简单介绍。
首先为了使用这些标签,需要在INSTALLED_APPS 中注册django .contrib.humanize
然后在模板中引用humanize:{% raw %}{% load humanize % }{% endraw %}

apnumber

将数字1 ~ 9 转换为英文单词,但是其他数字不转换,如数字10 将被原样输出。
示例:

1
2
3
数字1 被转换为one ;
数字2 被转换为two ;
数字10 仍显示10 ;

如果当前工程语言是中文的话,数字将会被转换为对应的汉字,例如:

1
2
3
4
5
{% raw %}
{{ 1|apnumber }}
{{ 2|apnumber }}
{{ 5|apnurnber }}
{% endraw %}

如果当前工程语言是中文的话,数字将会被转换为对应的汉字,例如:

输出:

1
2
3



intcomma

输出以逗号分隔的数字,如4500 输出4,500, 4500.2 输出4,500.2 。

intword

以文字形式输出数字,如1000000 输出“ 1.0 million ”, 1200000 输出“ 1,2 Million ” 。
对于中文系统,将会输出对应的中文,如1200000 输出" 1.2 百万” 。

naturalday

将当前日期以及前后一天输出为today 、yesterday 和tomorrow ,而中文系统分别输出
“今天”“昨天”和“明天” 。

naturaltime

对于日期时间格式,时间值与系统当前时间比较,然后输出结果。如当前时间输出
“ now ”, 29 秒前输出“ 29 sec onds ago ” 。如果使用naturaltime 输出今天、昨天、明天的话,
就会变成“现在”“ 23 小时·以后”“ 1 日之前” 。

ordinal

将数字转换为序数,如l 输出“ 1 st ”; 2 输出“ 2nd ”; 3 输出“ 3rd ” 。注意此时中文与
英文的输出一样。

自定义标签或过滤器

  1. 确认settings已配置当前应用(命令行创建应用时系统默认会将该应用添加至settingsINSTALLED_APPS中)
  2. 当前应用中创建templatetags目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
myproject
|——myproject
|——__init__.py
|——asgi.py
|——settings.py
|——urls.py
|——wsgi.py
|——templates
|——myapp
|——migrations
|——templatetags
|——__init__.py
|——diy_tags.py
|——manage.py
  1. 创建自己过滤器和标签文件,如myapp/templatetags/diy_tags.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django import template
from django.utils.safestring import mark_safe

# register的名字是固定的,不可改变
register = template.Library()

@register.filter
def filter_multi(v1, v2):
return v1*v2

@register.simple_tag
def tag_multi(v1, v2):
return v1 * v2

@register.simple_tag
def format_input(id, cls):
result = "<input type='text' id='%s' class='%s'>" % (id, cls)
return mark_safe(result)
  1. 在要使用自定义标签和过滤器的html模板中导入自定义标签文件
1
2
3
{% raw %}
{% load diy_tags %}
{% endraw %}
  1. 在模板中使用自定义标签和过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% raw %}
<h2>原始:{{ num }}</h2>
<h2>乘以2:{{ num|filter_multi:2 }}</h2>
<h2>乘以字符串,重复输出num个字符串:{{ num|filter_multi:"中国" }}</h2>
<h2>计算2*5 : {% tag_multi 2 5 %}</h2>
<h2>计算num*5 : {% tag_multi num 5 %}</h2>
<h2>判断num*2是否大于30:
# 注意:filter可以用在if等语句后,simple_tag不可以
{% if num|filter_multi:2 > 30 %}
大于30
{% else %}
小于30
{% endif %}
</h2>
<h2>输出表单:</h2>
{% format_input 'name_field' 'input_class' %}
{% endraw %}

num为22的情况下渲染出的效果如下:

img

模板继承

Django模版引擎中最强大也是最复杂的部分就是模版继承了。模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。

通过从下面这个例子开始,可以容易的理解模版继承:

我们创建一个基模板layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{% raw %}
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}我的应用{% endblock %}-托小尼</title>
</head>

<body>
<div id="sidebar">
{% block sidebar %}
<h2>导航栏</h2>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/rd/">开发</a></li>
</ul>
{% endblock %}
</div>

<div id="content">
{% block content %}我是内容{% endblock %}
</div>
</body>
</html>
{% endraw %}

当我们整体需要这样,只要改变某一块内容的时候,就可以继承它,我们假设为index.html

1
2
3
4
5
6
7
8
9
10
11
{% raw %}
{% extends 'layout.html' %}
{% block title %}个人开发技巧{% endblock %}
{% block sidebar %}
{{ block.super }}
继承父级导航,后面我添加更多内容
{% endblock %}
{% block content %}
我是文本正式内容
{% endblock %}
{% endraw %}

就这么简单,我们看下渲染后页面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>个人开发技巧-托小尼</title>
</head>

<body>
<div id="sidebar">


<h2>导航栏</h2>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/rd/">开发</a></li>
</ul>

继承父级导航,后面我添加更多内容

</div>

<div id="content">

我是文本正式内容

</div>
</body>
</html>

使用继承的一些提示:

  • 如果你在模版中使用 {% raw %}{% extends %}{% endraw %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。

  • 在base模版中设置越多的 {% raw %}{% block %}{% endraw %} 标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。

  • 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% raw %}{% block %}{% endraw %} 中。

  • 如果需要从父模板获取块的内容,则{{block.super}}变量将起作用。如果要添加到父块的内容,而不是完全重写父块,则这非常有用,应用场景如:基模板包含基本的css或者js静态资源,其他页面除了要使用这些基础的静态资源以外还要使用自己页面固定的静态资源,这个时候使用该变量可以很好地解决

  • 为了更好的可读性,你也可以给你的 {% raw %}{% endblock %}{% endraw %} 标签一个 名字 。例如:

1
2
3
4
5
{% raw %}
{% block content %}
...
{% endblock content %}
{% enbraw %}

在大型模版中,这个方法帮你清楚的看到哪一个  {% raw %}{% block %}{% endraw %} 标签被关闭了。

不能在一个模版中定义多个相同名字的 block 标签。