django基础之Django模板
Web模板系统
一个模板的渲染需要三方参与:
- 模板引擎: 主要参与模板渲染的系统
- 内容源: 输入的数据。比较常见的有数据库、XML文件和用户请求这样的网络数据
- 模板: 一般是和语言相关的文本
模板系统的工作过程如图所示

由图可知, 模板和内容源由模板引擎处理和组合, 批量生成Web文档。
模板介绍
在之前的章节中, 视图函数只是直接返回文本, 而在实际生产环境中其实很少这样用, 因为实际的页面大多是带有样式的HTML代码, 这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统, 其中最知名最好用的就是DTL和jinja2。DTL是Django Template Language三个单词的缩写, 也就是django自带的模板语言。当然也可以配置Django支持jinja2等其他模板引擎, 但是作为Django内置的模板语言, 和Django可以达到无缝衔接而不会产生一些不兼容的情况
DTL与普通的HTML的区别
DTL模板是一种带有特殊语法的HTML文件, 这个HTML文件可以被Django编译, 可以传递参数进去, 实现数据动态化。在编译完成后, 生成一个普通的HTML文件, 然后发送给客户端
渲染模板
渲染模板有多种方式, 这里讲下两种常见的方式
1. render_to_string: 找到模板, 然后将模板编译后渲染成Python的字符串格式。最后再通过HttpResponse类包装成一个HttpResponse对象返回回去。示例代码如下:
from django.template.loader import render_to_string from django.http import HttpResponse def book_detail(request, book_id): html = render_to_string('detail.html') return HttpResponse(html)
2. django还提供了一个更加简便的方式, 直接将模板渲染成字符串和包装成HttpResponse对象一步到位完成。示例代码如下:
from django.shortcuts import render def book_list(request): return render(request, 'list.html')
模板查找路径
在项目的settings.py文件中, 有一个TEMPLATES配置, 这个配置包含了模板引擎的配置, 模板查找路径的配置, 模板上下文配置等。模板路径可以在两个地方配置。
1. DIRS: 这是一个列表, 在这个列表中可以存放所有的模板路径, 以后在视图中使用render或者render_to_string渲染模板的时候, 会在这个列表的路居中查找模板
2. APP_DIRS: 默认为True, 这个设置为True后, 会在INSTALLED_APPS的安装了的APP下的templates文件夹中查找模板。
3. 查找顺序: 比如代码render('list.html')。先会DIRS在这个列表中依次查找路径下有没有这个模板,如果有,就返回。如果DIR列表中所有的路径都没有找到, 那么会先检查当前这个视图所处的app是否已经安装, 如果已经安装了, 那么就先在当前这个app下的templates文件夹中查找模板, 如果没有找到, 那么会在其他已经安装了的app中查找。如果所有路径下都没有找到,那么会抛出一个TemplatesDoesNotExist 的异常
模板变量
DTL模板语法
模板中可以包含变量, Django在渲染模板的时候, 可以传递变量对应的值去进行替换。变量的命名规范和Python非常类似, 只能是阿拉伯数字和英文字符以及下划线的组合, 不能出现标点符号等特殊字符。变量需要通过视图函数渲染, 视图函数是在使用render或者render_to_string的时候可以传递一个context的参数, 这个参数是一个字典类型。以后在模板中的变量就从这个字典中读取值的。示例代码如下:
# profile.html模板代码 <p>{{ username }}</p> # views.py代码 def profile(request): return render(request, 'profile.html', context={'username':'zhangjie'})
模板中的变量也支持点(.)的形式。在出现了点的情况, 比如person.username, 模板是按照以下方式进行解析的:
1. 如果person是一个字典, 那么就会查找这个字典的username这个key对应的值
2. 如果person是一个对象, 那么就会查找这个对象的username属性, 或者是username这个方法
3. 如果出现的是person.1, 会判断persons是否是一个列表或者元组或者任意可以通过下标访问的对象, 如果是的话就获取这个列表的第一个值。如果不是就获取到的是一个空的字符串
不能通过中括号的形式访问字典和列表中的值, 比如dict['key']和list[1]是不支持的!
因为使用点(.)语法获取对象的时候, 可以获取这个对象的属性, 如果这个对象是一个字典, 也可以获取这个字典的值, 因此不能在字典中定义字典本身就有的属性当做
key, 否则字典的那个属性将变成字典中的key了。
常用标签
if标签
if标签相当于Python中的if语句, 有else和elif相对应, 但是所有的标签都需要用标签符号({%%})进行包裹。if标签中可以使用==、!=、<、<=、>、>=、in、not in、is、is not等判断运算符。示例代码如下
{% if '张三' in persons %}
<p>张三</p>
{% else %}
<p>李刚</p>
{% endif %}
for...in...标签
for...in...类似于Python中的for...in...。可以遍历列表、元组、字符串、字典等一切可以遍历的对象。示例代码如下:
{% for person in persons %}
<p>{{ person.name }}</p>
{% endfor %}
如果想要反向遍历, 那么在遍历的时候就加上一个reversed。示例代码如下:
{% for person in persons reversed %}
<p>{{ person.name }}</p>
{% endfor %}
遍历字典的时候, 需要使用items、keys和values等方法。在DTL中, 执行一个方法不能使用圆括号的形式。遍历字典的代码如下:
{% for key, value in persons.item %}
<p>key:{{ key }}</p>
<p>value:{{ value }}</p>
{% endfor %}
在for循环中, DTL提供了一些变量可供使用。这些变量如下:
-
forloop.counter: 当前循环的下标, 以1作为起始值 -
forloop.counter0: 当前循环的下标, 以0作为起始值 -
forloop.revcounter: 当前循环的反向下标值。比如列表有5个元素, 那么第一次遍历这个属性是等于5, 第二次是4, 依次类推。并且是以1作为最后一个元素的下标 -
forloop.revcounter0: 类似于forloop.counter。不同的是最后一个元素的下标是从0开始 -
forloop.first: 是否第一次遍历 -
forloop.last: 是否是最后一次遍历 -
forloop.parentloop: 如果有多个循环嵌套, 那么这个属性代表的是上一级的for循环
模板中的for...in...标签没有continue和break语句, 这一点和Python中有很大的不同, 一定要记清楚!
for...in...empty标签
这个标签使用跟for...in是一样的。只不过是在遍历的对象如果没有元素的情况下, 会执行empty中的内容。示例代码如下:
{% for person in persons reversed %}
<p>{{ person.name }}</p>
{% empty %}
<p>{{ 暂时还没有人 }}</p>
{% endfor %}
with标签
在模板中定义变量。有时候一个变量访问的时候比较复杂, 那么可以先把这个变量缓存到一个变量上, 以后就可以直接使用这个遍历就可以了。示例代码如下:
context = { 'person': ['张三', '李四'] } {% with lisi=person.1 %} <p>{{ lisi }}</p> {% endwith %}
有几点需要强烈注意:
在
with语句中定义的变量, 只能在{% with %}{% endwith %}中使用, 不能在这个标签外面使用。定义变量的时候, 不能再等号左右两边留有空格。比如{% with lisi = person.1 %}是错误的
还有另外一种写法也同样支持
{% with person.1 as lisi %} <p>{{ lisi }}</p> {% endwith %}
url标签
在模板中, 我们经常要写一些url, 比如某个a标签中需要定义href属性。 当然如果通过硬编码的方式直接将这个url写死在里面也是可以的。但是这样对于以后项目维护可能不是一件好事。因此建议使用这种反转的方式来实现, 类似于django中的reverse一样。示例代码如下:
<a href="{% url 'book:list' %}">图书列表页面</a>
如果url反转的时候需要传递参数, 那么可以在后面传递。但是参数分位置参数和关键字参数。位置参数和关键字参数不能同时使用。示例代码如下:
# path部分 path('detail/<book_id>/', view.book_detail, name='detail') # url反转, 使用位置参数 <a href='{% url "book:detail" 1 %}'>图书详情页面</a> # url反转, 使用关键字参数 <a href="{% url 'book:detail' book_id=1 %}">图书详情页面</a>
如果想要在使用url标签反转的时候要传递查询字符串的参数, 那么必须要手动在后面添加。示例代码如下:
<a href='{% url "book:detail" 1 %}?page=1'>图书详情页面</a>
如果需要传递多个参数, 那么通过空格的方式进行分隔。示例代码如下:
<a href="{% url 'book:detail' book_id=1 page=2 %}">图书详情页面</a>
spaceless标签
移除html标签中的空白字符。包括空格、tab键、换行等。示例代码如下:
{% spaceless %}
<p>
<a href='foo/'>foo</a>
</p>
{% endspaceless %}
那么在渲染完成后, 会变成以下的代码:
<p><a href='foo/'>foo</a></p>
spaceless只会移除html标签之间的空白字符, 而不会移除标签与文本之间的空白字符。
{% spaceless %}
<strong>
Hello
</strong>
{% endspaceless %}
这个将不会移除strong中的空白字符
autoescape标签
开启和关闭这个标签内元素的自动转义功能。自动转义是可以将一些特殊的字符。比如<转义成html语法能识别的字符, 比如<会被转义成<, 而>会被转义成>。模板中默认是已经开启了自动转义的。autoescape的示例代码如下
# 传递的上下文信息 context = { 'info':'<a href="www.baidu.com">百度</a>', } # 模板中关闭自动转义 {% autoescape on %} {{ info }} {% endautoescape %}
那么就会显示百度的一个超链接。如果把on换成off, 那么就会显示成一个普通的字符串。示例代码如下:
{% autoescape off%}
{{ info }}
{% endautoescape %}
verbatim标签
默认在DTL模板中是会去解析哪些特殊字符的。比如{%和%}以及{{之间}}等。如果你再某个代码片段中不想使用DTL的解析引擎。那么可以把这个代码片段放在verbatim标签中。示例代码如下:
{% verbatim %}
{{if dying}}Still alive.{{/if}}
{% endverbatim%}
更多标签请参考官方文档:
https://docs.djangoproject.com/en/2.0/ref/templates/builtins
常用过滤器
在模板中, 有是有需要对一些数据进行处理以后才能使用。一般在Python中我们是通过函数的形式来完成的。而在模板中, 则是通过过滤器来实现的。过滤器使用的是|来使用。比如使用add过滤器, 那么示例代码如下:
{{ value|add:"2" }}
add
将传进来的参数添加到原来的值上面。这个过滤器会尝试将值和参数转换成整形然后进行相加。如果转换成整形的过程中失败了, 那么就会将值和参数进行拼接。如果是字符串, 那么就会拼接成字符串。如果是列表, 那么就会拼接成一个列表。示例代码如下:
{{ value|add:"2" }}
如果value是等于4, 那么结果将是6, 如果value是等于一个普通的字符串, 比如abc, 那么结果将是abc2,add过滤器的源码如下:
def add(value, arg): """Add thr arg to the value.""" try: return int(value) + int(arg) except (ValueError, TypeError): try: return value+arg except: return ''
cut
移除值中所有指定字符串, 类似于Python中的replace(arg, "")。示例代码如下:
{{ value|cut:" "}}
以上示例将会移除value中所有的空格字符串。cut过滤器的源代码如下:
def cut(value, arg): """ Remove all values of arg from the given string.""" safe = isinstance(value, SafeData) value = value.replace(arg, '') if safe and arg != ';': return mark_safe(value) return value
date
将一个日期按照指定的格式, 格式化成字符串。示例代码如下:
# 数据 context = { 'birthday': datetime.now(), } # 模板 {{ birthday|date: "Y/m/d" }}
那么将会输出2018/02/01。其中Y代表的是四位数字的年份。m代表的是两位数字的月份。d代表的是两位数字的日。还有更多时间格式化的方式。见如下表
| 格式字符 | 描述 | 示例 |
|---|---|---|
| Y | 四位数字的年份 | 2018 |
| m | 两位数字的月份 | 01-12 |
| n | 月份, 1-9之前没有0前缀 | 1-12 |
| d | 两位数字的天 | 01-31 |
| j | 天, 但是1-9前面没有0前缀 | 1-31 |
| g | 小时, 12小时格式的, 1-9之前没有0前缀 | 1-12 |
| h | 小时, 12小时格式的, 1-9之前有0前缀 | 01-12 |
| G | 小时, 24小时格式的, 1-9之前没有0前缀 | 1-23 |
| H | 小时, 24小时格式的, 1-9之前有前缀 | 01-23 |
| i | 分钟, 1-9之前有0前缀 | 00-59 |
| s | 秒, 1-9之前有0前缀 | 00-59 |
default
如果值被评估为False。比如[]、""、None、{}等这些在if判断中为False的值, 都会使用default过滤器提供的默认值。示例代码如下:
{{ value|default:'nothing' }}
如果value是等于一个空的字符串。比如"", 那么以上代码将会输出nothing。
default_if_none
如果值是None, 那么将会使用default_if_none提供的默认值。这个和default有区别, default是所有评估为False的都会使用默认值。而default_if_none则只有这个值是等于None的时候才会使用默认值。示例代码如下:
{{ value|default:"nothing" }}
如果value是等于""也即空字符串, 那么以上将会输出空字符串。如果value是一个None值,以上代码才会输出nothing
first
返回列表/元组/字符串中的第一个元素。示例代码如下:
{{ value|first }}
如果value是等于['a', 'b', 'c'], 那么输出将会是a。
last
返回列表/元组/字符串的最后一个元素。示例代码如下:
{{ value|last }}
如果value是等于['a', 'b', 'c'], 那么输出将会是c。
使用四舍五入的方式格式化一个浮点类型。如果这个过滤器没有传递任何参数。那么只会在小数点后保留一个小数, 如果小数后面全是0, 那么只会保留整数。当然也可以传递一个参数, 标识具体要保留几个小数。
1. 如果没有传递参数
| 模板代码 | 输出 | |
|---|---|---|
| 34.23234 | {{ value|floatformat}} | 34.2 |
| 34.000 | {{ value|floatformat }} | 34 |
| 34.260 | {{ value|floatformat }} |
2. 如果传递参数
| value | 模板代码 | 输出 |
|---|---|---|
| 34.23234 | {{ value|floatformat:3}} | 34.232 |
| 34.00000 | {{value|floatformat:3}} | 34.000 |
| 34.26000 | {{value|floatformat:3}} | 34.260 |
join
类似于Python中的join, 将列表/元组/字符串用指定的字符进行拼接。示例代码如下:
{{ value|join '/'}}
如果value是等于['a', 'b', 'c'], 那么上面代码将输出a/b/c
length
获取一个列表/元组/字符串/字典的长度。示例代码如下:
{{ value|length }}
如果value是等于['a', 'b', 'c'], 那么上面代码将输出3。如果value为None, 那么以上将返回0
lower
将值中所有的字符全部转换成小写。示例代码如下:
{{ value|lower }}
upper
将值中所有的字符全部转换成小写。示例代码如下:
{{ value|upper }}
如果value是等于Hello World。那么以上代码将输出HELLO WOLRD
random
在被给的列表/字符串/元组中随机选择一个值。示例代码如下:
{{ value|random }}
如果value是等于['a', 'b', 'c'], 那么以上代码会在列表中随机选择一个
safe
标记一个字符串是安全的。也即会关掉这个字符串的自动转义。示例代码如下:
{{ value|safe }}
如果value是一个不包含任何特殊字符的字符串, 比如<a>这种, 那么以上代码将会把字符串正常输入。如果value是一串html代码, 那么以上代码将会把这个html代码渲染到浏览器中
slice
类似于Python中的切片操作。示例代码如下:
{{ some_list|slice:'2:'}}
stringtags
删除字符串中所有的html标签。示例代码如下:
{{ value|stringtags }}
如果value是<strong>hello world</strong>, 那么以上代码将会输出hello world
truncatechars
如果给定的字符串长度超过了过滤器指定的长度。那么将会进行切割, 并且会拼接三个点来作为省略号。示例代码如下:
{{ value|truncatechars:5 }}
如果value等于北京欢迎您~, 那么输出结果是北京...
truncatechars_html
类似于truncatechars, 只不过是不会切割html标签。示例代码如下:
{{ value|truncatechars_html }}
如果value等于<p>北京欢迎您~</p>, 那么输出结果是<p>北京...</p>
自定义过滤器
自定义过滤器介绍
虽然DTL给我们内置了许多好用的过滤器。但是有些时候还是不能满足我们的需求。因此Django给我们提供了一个接口, 可以让我们自定义过滤器, 实现自己的需求
模板过滤器必须要放在app中, 并且这个app丙戌要在INSTALLED_APPS中进行安装。然后在这个app下面创建一个Python包叫做templatetags。再在这个包下面创建一个python文件。比如app的名字叫做book, 那么项目结构如下:
- book - views.py - urls.py - models.py -templatetags - my_filter.py
在创建了存储过滤器的文件后, 接下来就是在这个文件中写过滤器了。过滤器实际上就是python中的一个函数, 只不过把这个函数注册到模板库中, 以后就可以使用这个函数了。但是这个函数的参数有限制, 第一个参数必须是这个过滤器需要处理的值, 第二个参数可有可无, 如果有, 那么就意味着在模板中可以传递参数。并且过滤器的函数最多只能有两个参数。在写完过滤器后, 再使用django.template.Library对象注册进去。示例代码如下:
from django import template # 创建模板库对象 register = template.Library() # 过滤器函数 def mycut(value, mystr): return value.replace(mystr) # 将函数注册到模板库中 register.filter('mycut', mycut)
以后想要在模板中使用这个过滤器, 就要在模板中load以下这个过滤器所在的模块的名字(也就是这个python文件的名字)。示例代码如下:
{% load my_filter %}
自定义时间过滤器
有时候经常会在朋友圈、微博中可以看到一条信息发表的时间, 并不是具体的时间, 而是距离现在多久。比如刚刚、1分钟前等。这个功能DTL是没有内置这样的过滤器的, 因此我们可以自定义一个这样的过滤器。示例代码如下:
# time_filter.py文件 from datetime import datetime from django import template register = template.Library() def time_since(value): if isinstance(value, datetime): now = datetime.now() timestamp = (now-value).total_seconds() if timestamp < 60: return '刚刚' elif timestamp < 60*60: minutes = int(timestamp/60) return '%s分钟前'%minutes elif timestamp < 60 * 60 * 24: hours = int(timstamp/(60*60)) return '%s小时前'%hours elif timestamp < 60 * 60 * 24 * 30: days = int(timestamp/(60*60*24)) return '%s天前'%days else: return value.strftime('%Y/%m/%d %H:%M') else: return value register.filter('time_since', time_since)
在模板中使用的示例代码如下:
{% load time_filter %}
...
{% value|time_since %}
...
为了更加方便的将函数注册到模板库中当做过滤器。也可以使用装饰器来讲一个函数包装成过滤器。示例代码如下:
from django import template register = template.Library() @register.filter(name='mycut') def mycut(value, mystr): return value.replace(mystr, '')
模板结构优化
引入模板
有时候一些代码在许多模板中都用到的。如果我们每次重复的去拷贝代码那肯定不符合项目的规范。一般我们可以把这些重复的代码抽取出来。就类似于Python中的函数一样, 以后想要使用这些代码的时候, 就通过include包含进来。这个标签就是include。示例代码如下:
# header.html <p>我是header</p> # footer.html <p>我是footer</p> # main.html {% include 'header.html' %} <p>我是main内容</p> {% include 'footer.html' %}
include标签寻找路径的方式, 也是跟render渲染模板的函数是一样的
默认include标签包含模板, 会自动的使用主模板中的上下文, 也即是自动的使用主模板中的变量。如果想要传入一些其他的参数, 那么可以使用with语句, 示例代码如下:
# header.html <p>用户名: {{username}}</p> # main.html {% include 'header.html' with username='feather' %}
模板继承
在前端页面开发中。有些代码是需要重复使用的。这种情况可以使用include标签来实现。也可以使用另一种比较强大的方式来实现, 那就是模板继承。模板继承类似于Python中的类, 在父类中定义可以先定义一些变量和方法, 然后在子类中实现。模板继承也可以在父模板中定义好一些子模板需要用到的代码, 然后子模板直接继承就可以了。并且因为子模板肯定有自己不同的代码, 因此可以在父模板中定义一个block接口, 然后子模板再去实现。以下是父模板的代码:
{% load static %}
<!DOCTYPE html>
<html lang='en'>
<head>
<link rel="stylesheet" href="{% static 'style.css' %}"/>
<title>{% block title%}我的站点{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">首页</a></li>
<li><a href="/blog/">博客</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
这个模板, 我们取名为base.html, 定义好一个简单的html骨架, 然后定义好两个block接口, 让子模板来根据具体需求来实现。子模板然后通过extends标签来实现, 示例代码如下:
{% extends 'base.html' %}
{% block title %}博客列表{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
注意:
1. extends标签必须放在模板的第一行。
2. 子模板的代码必须放在block中, 否则不会被渲染
如果在某个block中需要使用父模板的内容, 那么可以使用{{block.super}}来继承。比如上例, {% block title %}, 如果想要使用父模板的title, 那么可以在子模板的title block中使用{{block.super}}来实现
在定义block的时候, 除了在block开始的地方定义这个block的名字, 还可以在block结束的时候定义名字。比如{% block title %}{% endblock title %}。这在大型项目模板中显得尤其有用, 能让你快速的看到block包含在那里
加载静态文件
在一个网页中。不仅仅只有一个html的骨架, 还需要css样式文件, js执行文件以及一些图片等。因此在DTL中加载静态文件是一个必须要解决的问题。在DTL中, 使用static标签, 首先需要{% load static %}。加载静态文件的步骤 如下:
1. 首先确保django.contrib.staticfiles已经添加到settings.INSTALLED_APPS中
2. 确保在settings.py中设置STATIC_URL
3. 在已经安装了的app中创建一个文件夹叫做static, 然后在这个static文件夹下创建了一个当前app的名字的文件夹, 再把静态文件放到这个文件夹下。例如你的app叫book, 有一个静态文件叫做app.png, 那么路径为book/static/book/aa.png(为什么在app下创建一个static文件夹, 还需要在这个static下创建一个同app名字的文件夹呢?原因是如果直接把静态文件放到static文件夹下, 那么在模板加载静态文件的时候就是使用aa.png, 如果多个在多个app之间有同名的静态文件, 这时候就很容易产生混淆。而在static文件夹下创建一个同名app的文件夹, 在模板中加载的时候就是使用book/aa.png, 这样就可以避免混淆了)
4. 如果有一些静态文件是不和任何app挂钩的。那么可以在setting.py中添加STATICFILES_DIRS, 以后DTL就会在这个列表中查找静态文件。比如说可以设置:
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ]
5. 在模板中使用load标签加载static标签。比如要加载在项目的static文件夹下的style.css的文件, 那么示例代码如下:
{% load static %}
<link rel="styleheet", href="{% static "style.css" %}">
6. 如果不想每次在模板中加载静态文件都使用load加载static标签, 那么可以在settings.py中的TEMPLATES/OPTIONS添加'builtins':['django.templatetags.static'], 这样以后在模板中就可以直接使用static标签, 而不用手动的load了
7. 如果没有在settings.INSTALLED_APPS中添加django.contrib.staticfiles。那么我们就需要手动的将请求静态文件的url与静态文件的路径进行映射了。示例代码如下:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # 其他的url映射 ]+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

浙公网安备 33010602011771号