模版层
模板层#
模板简介#
每一个Web框架都需要一种很便利的方法用于动态生成HTML页面, 最常见的做法是使用模板。
模板包含所需HTML页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入静态部分。
简而言之, 模板层就是往HTML文件中填入动态内容的系统, 这部分内容通常可能需要计算,查询数据库等方式才可以获得.
Django自带一个称为DTL(Django Template Language )的模板语言,以及另外一种流行的Jinja2语言(需要提前安装). Django为加载和渲染模板定义了一套标准的API,与具体的后台无关。加载指的是,根据给定的模版名称找到的模板然后预处理,通常会将它编译好放在内存中。渲染则表示,使用上下文(Context)数据对模板插值并返回生成的字符串.
模板语法中最重要的就是这两种特殊符号了
- {{ }} 插值表达式, 里面放置的是后台传递过来的上下文变量. 变量后还可以跟过滤器
- {% %} 与逻辑相关的处理, 如标签, 导入模板文件, 加载静态文件等...
变量#
以下是来自官方文档的翻译.
在Django的模板语言中按此语法使用:{{ 变量名 }}
当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身. 变量的命名包括任何字母数字以及下划线 _
的组合。 变量名称中不能有空格或标点符号.
.
在模板语言中有特殊的含义。当模版系统遇到点.
,它将以这样的顺序查询:
-
字典查询(Dictionary lookup)
-
属性或方法查询(Attribute or method lookup)
-
数字索引查询(Numeric index lookup)
当使用的变量不存在的时候, Django会自动用
''
来代替.
模板语法还支持循环判断等逻辑功能, 具体使用方式看下面的例子
视图代码
# views.py
def tmp(request):
# 测试一些基础数据类型
integer = 100
string = 'hello world'
lst = [1, 2, 3, 4, 4]
dic = {'name': 'root', 'password': '123'}
set_ = {'a', 'b', 'c', 'd'}
# 测试特殊对象
def func():
return 'func无参数'
def func3():
pass
class Foo:
def __str__(self):
return 'Foo object'
# 这里一般需要我们自己主动创建一个字典, 然后把使用到的所有变量丢进去
# 当做上下文传递给模板文件, 这里图省事, 直接使用locals变量了.
return render(request, 'tmp.html', locals())
模板代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>整数: {{ integer }}</p>
<p>字符串: {{ string }}</p>
<p>字典: {{ dic }}</p>
<p>列表: {{ lst }}</p>
<p>集合: {{ set_ }}</p>
<p>无参函数有返回值: {{ func }}</p>
<p>无参函数无返回值: {{ func3 }}</p>
<p>自定义类的实例对象: {{ Foo }}</p>
<div>
<p>for循环列表</p>
<ul>
{% for l in lst %}
<li>{{ l }}</li>
{% endfor %}
</ul>
<p>for循环字典</p>
<ol>
{% for k, v in dic.items %}
<li>{{ k }}: {{ v }}</li>
{% endfor %}
</ol>
</div>
</body>
</html>
浏览器的结果
上面的结果证明了我们几乎可以传任何类型的变量到模板中渲染, 需要注意的是调用函数或对象方法时不支持在后面传参数. 直接渲染对象是调用__str__
方法, 渲染方法/函数是直接加括号调用.
过滤器#
过滤器是用来修改变量的显示.
语法结构是: {{ value|filter: [参数] }}, 过滤器支持传一个参数或不传参.
|
的作用和linux的管道符功能一样, 将前面的结果当做参数传递给后面的过滤器使用.
过滤器的注意点:
- 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入. 如
{{ text|escape|linebreaks }}
- 过滤器可以接受参数,例如:
{{ sss|truncatewords:30 }}
,这将显示sss的前30个词。 - 过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
|
左右没有空格没有空格没有空格
常用的过滤器
default#
类似字典里的get方法. 如果一个变量是false或者为空,使用给定的默认值. 否则,使用变量的值.
{{ value|default:"nothing"}}
当value为空的时候, 显示nothing
length#
类似于len内置方法. 返回列表或字符串的长度.
{{ value|length }}
如果value的值是['a', 'b', 'c', 'd'], 结果就是4
filesizeformat#
当需要展示一个大文件的大小的时候, 传入字节数, 格式化成我们更容易看的单位. 例如vaule的值为123456789, 返回117.7MB.
date#
类似于time.strftime的格式化. 使用语法和格式化参数稍有不同.
例如 {{ now|date:'Y-m-d H:i:s' }}
--> 2019-09-24 23:28:09
使用格式化的时候不需要再传递%, 分和秒的显示也稍有不同, 更详细的参数见官网date过滤器.
safe#
当我们传递给模板的是HTML代码的时候, 需要指定safe过滤器才能正确的按照html标签格式解析出来, 毫无疑问这样做是为了安全, 如果我们确保渲染的的代码不包含恶意script脚本的时候, 可以指定这个过滤器.
更多过滤器#
参考官方文档
标签#
模版语言中的标签类似Python中的函数,功能多样,使用灵活; 可以输出内容、控制结构,甚至可以访问其他的模板标签.
标签的通用语法是 {% tag %}, 下面是常用的内置标签.
for#
<ol>
{% for k, v in dic.items %}
<li>{{ k }}: {{ v }}</li>
{% endfor %}
</ol>
上面是通过for循环输出字典k, v键值对. for标签是一个特殊的标签, 中间的内容是包裹在 {% for %} 和{% endfor %}中.
特殊的, 在对可迭代对象进行遍历的过程中, 我们还可能经常需要访问索引, 这就需要用到模板内置的forloop对象.
{% for l in lst %}
<p>{{ forloop }}</p>
{% endfor %}
# 浏览器输出结果
{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 5, 'revcounter0': 4, 'first': True, 'last': False}
{'parentloop': {}, 'counter0': 1, 'counter': 2, 'revcounter': 4, 'revcounter0': 3, 'first': False, 'last': False}
...
变量 | 描述 |
---|---|
forloop.counter | 当前循环的索引(从1开始) |
forloop.counter0 | 当前循环的所用(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(从1开始) |
forloop.revcounter0 | 当前循环的倒序索引值(从0开始) |
forloop.first | 当前循环是不是第一次循环(布尔值) |
forloop.last | 当前循环是不是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
for ... empty#
这是一个可选参数, 当循环为空的时候, 输出里面的内容.
if ... elif ... else ... endif#
if
的语法与Python的if
语法一模一样, 支持的布尔型操作也几乎一致, 下面的几种方式都支持.
逻辑运算符
==
, !=
, <
, >
, <=
, >=
, in
, not in
, is
, is not
# in运算符
{% if "bc" in "abcdef" %}
This appears since "bc" is a substring of "abcdef"
{% endif %}
# is运算符
{% if somevar is None %}
This appears if somevar is None, or if somevar is not found in the context.
{% endif %}
# ==
{% if somevar == "x" %}
This appears if variable somevar equals the string "x"
{% endif %}
# if elif else
{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
Athletes should be out of the locker room soon!
{% else %}
No athletes.
{% endif %}
过滤器
if
语句后还可以跟过滤器来进行判断
{% if now|date:'Y-m-d' > '2018-02-03' %}
<p>if可以添加过滤器进行判断</p>
{% endif %}
更复杂的表达式
if
语句还支持更复杂的逻辑表达式, 与或非操作
and
not
or
{% if a == b or c == d and e %}
csrf_token#
这是一个跨站请求伪造保护的标签. 在页面的任何地方写上这个标签, 就会生成一个隐藏的input
标签, 如果在form表单里面, 它会一起提交到后端, post请求不提交它的话, 就会报403禁止访问错误.
语法结构是{% csrf_token %}
最后在页面生成下面这个隐藏input标签.
<input type='hidden' name='csrfmiddlewaretoken' value='3ZxGWK8bsJkpXGV9WEikOP0VwjhyEoZ6qMVwaizOdFFMDoXrtJTWJUdCWBoPjeyk' />
更多标签#
参考官方文档
自定义过滤器与标签#
自定义过滤器与标签需要有以下的目录结构
# 目录的层级结构
app/
__init__.py
models.py
templatetags/
__init__.py
my_tag.py
views.py
通常有以下步骤
- 在app目录下创建一个
templatetags
包或文件夹, 文件名必须是这个 - 在该包下可以创建任意的
py
文件, 这个就是我们自定义的标签或过滤器文件. - 在该文件下就可以写过滤器函数和标签函数了
自定义标签与过滤器还需要注意, 需要需要在文件开头写下面两行代码
from django import template
register = template.Library()
这样我们就可以通过register对象的filter, simpletag方法来装饰注册我们的自定义标签过滤器.
最后不要忘记需要在模板中导入我们的自定义过滤器或标签文件 {% load mytag %}
导入自定义标签过滤器之后, 我们就可以正常像使用内置标签一样使用自定义的了.
自定义过滤器#
使用过滤器需要使用register的filter函数来装饰我们的自定义函数
from django import template
register = template.Library()
@register.filter(name='time_format')
def time_format(date, fmt='%Y-%m-%d'):
# date: 一个datetime对象
return date.strftime(fmt)
# 模板中导入就可以正常使用了
{% load mytag %}
<p>{{ t|time_format }}</p>
<p>{{ t|time_format:'%Y-%m-%d %X' }}</p>
过滤器支持传递1个或不传参数, 如果需要传递多个参数, 可以传递一个字符串, 以,
分隔多个参数.
@register.filter(name='my_add')
def add(value, args):
"""过滤器不支持传递多个参数, 如果我们需要传递多个参数
可以传递以,或其他分隔符区分的一个字符串参数
"""
try:
num_list = [int(n) for n in args.split(',')]
except ValueError:
return ''
else:
return value + sum(num_list)
# 模板
<p>{{ 10|my_add:'1,2,3,4' }}</p>
自定义标签#
自定义标签需要使用register的simple_tag函数来装饰我们的自定义函数.
同样以上面的显示时间来举例子
@register.simple_tag(name='current_time')
def current_time(fmt='%Y-%m-%d'):
return datetime.now().strftime(fmt)
# <p>{% current_time %}</p>
使用标签就需要使用使用{% %}来加载了, 此外标签内还支持传入任意的参数.
inclusion_tag#
我们还可以注册inclusion_tag标签, 通过引入这个标签, 可以动态导入一个HTML代码片段..
@register.inclusion_tag('temp_list.html', name='temp_list')
def temp_list(n):
"""动态生成一个小页面, 是由n个li选项组成的列表组"""
lst = list(range(1, n + 1))
return {'lst': lst}
<!-- temp_list.html -->
<ul>
{% for l in lst %}
<li>第{{ l }}项</li>
{% endfor %}
</ul>
{% load mytag %}
<p>inclusion tag 片段</p>
{% temp_list 5 %}
最后在模板里面导入标签, 再传入参数就可以动态的渲染出来我们需要的页面.
模板导入与继承#
模板导入#
模板导入的形式与inclusion_tag标签类似, 都是导入一个html页面片段. 只不过模板导入的是一个静态页面, 而inclusion_tag导入的是根据参数动态渲染的动态页面.
使用模板的导入的语法是: { % include '模板页面' %}
模板的导入语法非常简单, 先定义一个temp页面
<p>这里是temp页面</p>
然后在需要使用到这个组件的页面里直接导入即可
{% include 'base.html' %}
模板继承#
模板的继承概念与Python的继承概念类似, 都是为了减少代码的重复与冗余. 当我们有许多页面的部分地方相同, 只需要修改一部分内容, 这时候就需要用到模板的继承. 下面官方文档的说明:
Django模版引擎中最强大也是最复杂的部分就是模版继承了。模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
这个模版,我们把它叫作 base.html
, 它定义了一个可以用于两列排版页面的简单HTML骨架。“子模版”的工作是用它们的内容填充空的blocks。
在这个例子中, block
标签定义了三个可以被子模版内容填充的block。 block
告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。
子模版可能看起来是这样的:
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
extends
标签是这里的关键。它告诉模版引擎,这个模版“继承”了另一个模版。当模版系统处理这个模版时,首先,它将定位父模版——在此例中,就是“base.html”。
那时,模版引擎将注意到 base.html
中的三个 block
标签,并用子模版中的内容来替换这些block
。
请注意,子模版并没有定义 sidebar
block,所以系统使用了父模版中的值。父模版的 {% block %}
标签中的内容总是被用作备选内容(fallback)。
这种方式使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如,部分范围内的导航。
总结一下, 在模板的继承中, 通常块分的越多越好, 而且一般一个页面至少有3块, css块, js块, 和content块. 这样分以后扩展性也越好.