1-Django - 模板
before
Django2.2 + Python3.6.8
模板语法
Django模板中有两个特殊的模板语法:
{{}}
,两个花括号内为变量。在模板渲染中会替换成相应的变量值。{%%}
,花括号内包含百分号,称为标签。表示逻辑相关的操作,比如说for循环,if判断。
变量名由字母数字和下划线组成。
点.
在模板语言中有特殊的含义,用来获取对象的属性。
模板中的语法一般用以下几种形式,比如我们在views.py
中返回了如下内容:
# views.py
from django.shortcuts import render, HttpResponse
def index(request)
l = ['zhang', 'kai']
d = {'name': '张开'}
class A:
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
return '扯淡'
zhangkaitui = A('张开腿', 18)
zhangkaizui = A('张开嘴', 18)
obj = [zhangkaitui, zhangkaizui]
return render(request, 'index.html', {'l': l, 'd': d, 'obj': obj})
再来看前端页面都能取哪些值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<!-- 取对象l中的第一个元素 -->
<p>{{ l.0 }}</p>
<!-- 取字典中指定key的值 -->
<p>{{ d.name }}</p>
<!-- 取对象中的属性,因为对象被封装成列表,所以要先取0索引对应的元素对象 -->
<p>{{ obj.0.name }}</p>
<!-- 只能调用不带参数的方法 -->
<p>{{ obj.1.talk }}</p>
</body>
</html>
注意,当Django中的模板遇到.
时,会按照如下顺序查找:
- 在字典中查询
- 属性或者方法
- 数字索引
模板文件查询规则
这一小结主要来说下模板文件,也就是render的html文件,django后台是如何查找的,它的查询顺序是怎样的。
PS:如果学完一个项目中多app的使用和路由相关的知识来看,更合适。
首先我们来看下settings.py的关于模板的配置:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 请每次创建完一个django项目之后,都检查下这个路径,确保这个路径是完备的
'APP_DIRS': True, # 允许django去app中寻找模板文件
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
上面主要有两个参数是我们需要注意的:
DIRS
:这个是我们模板文件存放的路径,一般在项目根目录下,不过每当你创建django项目之后,就来检查下这个路径,看看是否有问题。APP_DIRS
:当值为True
时,允许django去app中寻找模板文件,也就是说,当你各个app都有自己单独的模板文件时,需要将这个参数设置为True
。
django在寻找模板文件时:
- 首先会寻找
DIRS
指定的模板文件,你也可以认为是公共的模板文件存放目录。 - 按照settings中
INSTALLED_APPS
中注册app的顺序去寻找模板文件,所以,并不是每个app会优先寻找自己app内部的模板文件。
如果建议,各个app内如果有自己的模板文件,那么建议在各自app内的templates目录内再创建一个目录,用来存放你的模板文件,防止出现被别的app中同名的模板文件截胡的情况出现。
过滤器
Django自带过滤器
过滤器(filter)一般用来修改变量展示的结果。
语法:
# 无参过滤器
{{ 变量|过滤器名字 }}
# 有参过滤器
{{ 变量|过滤器名字:"参数" }}
注意:过滤器中:
的左右两侧没有空格!
过滤器中的常用方法
- filesizeformat:将值对应的格式转化为
人类可读
的形式。
# views.py
value = 123456789
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value|filesizeformat }}</p>
展示结果是117.7 MB
。
- safe:为了安全,Django会对HTML中的js等语法进行自动转义,但有些情况下我们不希望被转移,就要告诉Django这段代码是安全的,不需要转义。
# views.py
value = "<a href='#'>Go !</a>"
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value|safe }}</p>
<p>{{ value }}</p>
如果不使用safe
转义,则会将a标签展示为普通的字符串。
- date:日期格式化
# views.py
import datetime
value = datetime.datetime.now()
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value }}</p> <!-- June 3, 2019, 5:22 p.m. -->
<p>{{ value|date:"Y-m-d H:i:s" }}</p> <!-- 2019-06-03 17:22:31 -->
<p>{{ value|date:"Y/m/d H:i:s" }}</p> <!-- 2019/06/03 17:22:31 -->
其他方法:
方法 | 描述 | 示例 |
---|---|---|
default | 如果变量后端没有传或者为空,可以给个默认值 | {{value|default:"zhangkai"}} |
lower | 小写 | {{ value|lower }} |
upper | 大写 | {{ value|upper }} |
title | 标题 | {{ value|title }} |
ljust | 左对齐 | {{ value|ljust:"10" }} |
rjust | 右对齐 | {{ value|rjust:"10" }} |
center | 居中 | {{ value|center:"15" }} |
length | 返回value的长度 | {{ value|length }} |
slice | 切片 | {{ value|slice:"1:-1" }} |
first | 获取第一个元素 | {{ value|first }} |
last | 获取最后一个元素 | {{ value|last }} |
join | 字符串拼接 | {{ value|join:"-" }} |
truncatechars | 如果字符串字符多于指定的字符数量, 那么会被截断。 截断的字符串将以可翻译的省略号序列( ... )结尾。 |
{{ value|truncatechars:"3"}} |
truncatewords | 以空格做截断符号,截断单词 | {{value|truncatewords:"3"}} |
cut | 删除指定字符,比如删除一段字符串中的空格 | {{value|cut:" "}} |
自定义过滤器
自定义过滤器在非前后端分离项目中有应用。
步骤:
- settings.py中的
INSTALLED_APPS
列表中,配置app,不然Django无法找到自定义过滤器。 - 在app中创建
templatetags
目录,注意,目录名只能叫做templatetags
。 templatetags
目录内,创建任意py文件,比如mytag.py
,然后写你自定义的逻辑。- 前端页面使用
{{}}
引入。
views.py
:
from django.shortcuts import render, HttpResponse, redirect
def books(request):
name = '张开'
return render(request, 'books.html', {"name": name})
mytag.py
:
from django import template
register = template.Library() # 注册器,变量名必须叫做register
@register.filter # 被装饰的foo就成了Django的过滤器
def foo(v1):
return v1
@register.filter # 被装饰的bar就成了Django的过滤器
def bar(v1, v2): # 自定义过滤器最多只能有两个参数
return v1 + v2
"""
在自定义过滤器中:
- 第一个参数是使用过滤器时,管道符前面的值 {{ name|foo }}
- 第二个参数是使用过滤器时,冒号后面的值 {{ name|bar:"v2" }}
"""
books.html
:
<!DOCTYPE html>
<html lang="en">
<head>
{% load mytag %} <!-- 需要load引入自定义过滤器所在的文件 -->
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 如果过滤器定义时只定义了一个形参,那么这里使用时,可以不传参,默认会将前面的值传进去,如下面的foo过滤器会自动的将name传递给形参v1 -->
{{ name|foo }}
<!-- 如果过滤器定义时定义了2个形参,那么这里使用时,可以只传一个值,如下面的bar过滤器会自动的将name传递给形参v1,bar后面的"v2"传递给形参v2-->
{{ name|bar:"v2" }}
</body>
</html>
include组件
组件是一个封装完整的功能模块,哪里需要直接include
引入就可以了。
form.html
<form action="" method="post">
<input type="text" name="username" placeholder="user">
<input type="password" name="password" placeholder="pwd">
</form>
books.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
{% include 'form.html' %} <!-- 引入组件 -->
</div>
</body>
</html>
tags
什么是模板标签?
模板标签是在{% %}
中实现的固定写法,常见的模板标签有{% load xxxx %}
、{% block xxxx %}
、{% if xxxx %}
等,通常都有{% endxxxx %}
作为结束,当然有的可能没有。
模板标签本质上是函数,标签名可以理解为函数名,模板标签的作用是包括载入代码、渲染模板、简单的逻辑判断或计数。
if语句
模板中的if语句用户简单的判断,有if/else
和if/elif/else
几种形式:
# views.py
l2 = [1, 0, 1, 2]
后端views.py
返回了上述列表。前端展示结果如下:
{% if l2 %}
<p>{{ l2.0 }}</p>
{% elif l2.1 %}
<p>{{ l2.1 }}</p>
{% elif l2.2 %}
<p>{{ l2.2 }}</p>
{% else %}
<p>l2</p>
{% endif %}
if语句支持and
、or
、==
、>
、<
、!=
、<=
、>=
、not in
、is
、is not
判断,注意运算符两边都要有空格。
for语句
模板中的for循环主要用于循环展示后端传过来的可迭代对象,用法如下示例:
{% for foo in list_obj %}
<p>{{ foo }}</p>
{% empty %}
<p>当循环对象为空或者没有这个变量时,显式empty内容</p>
{% endfor %}
{% for k,v in dict_obj.items %} <!--也可以 dict_obj.keys dict_obj.values-->
<p>{{ k }} {{ v }}</p>
{% empty %}
<p>当循环对象为空或者没有这个变量时,显式empty内容</p>
{% endfor %}
for循环中可用的参数:
Variable | Description |
---|---|
forloop.counter | 当前循环的索引值(从1开始) |
forloop.counter0 | 当前循环的索引值(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(到1结束) |
forloop.revcounter0 | 当前循环的倒序索引值(到0结束) |
forloop.first | 当前循环是否是第一次循环(布尔值) |
forloop.last | 当前循环是否是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
来看一些示例:
{% for foo in l %}
<p>{{ forloop.counter }} - {{ foo }}</p>
{% endfor %}
上例是一个简单的for循环。
再来看一个相对复杂的:
# views.py
l1 = ['姓名', '性别', '年龄']
l2 = [
['王二', '男', 18],
['李二', '男', 29],
['小红', '女', 29],
['小明', '男', 29],
]
后端views.py
返回了上述两个列表,前端展示结果如下:
<table border="1">
<thead>
<tr>
{% for foo in l1 %}
<td>{{ foo }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for bar in l2 %}
<tr>
{% for item in bar %}
{% if forloop.parentloop.counter|divisibleby:"2" %}
<td style="color: red">{{ item }}</td>
{% else %}
<td>{{ item }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
上例中,divisibleby
参数的意思是如果forloop.parentloop.counter
的值能被整除则返回True
。也即是执行这个if
语句。
with语句
如果后端有一个很深的嵌套结构。那么每次在使用其中的变量(变量名也很长)的时候,就比较麻烦。这时可以使用with
来为这个别名指定一个别名。
# views.py
d = {'k1': {'k2': {'k3': {'name': '张开'}}}}
后端views.py
返回了上述字典。前端展示结果如下:
<!-- 写法1 -->
{% with d.k1.k2.k3.name as name %}
<p>{{ name }}</p>
{% endwith %}
<p>{{ name }}</p> <!-- 别名name只能用于with中 -->
<!-- 写法2 -->
{% with name=d.k1.k2.k3.name %}
<p>{{ name }}</p>
{% endwith %}
<p>{{ name }}</p> <!-- 别名name只能用于with中 -->
csrf
我们很可能会面临跨站请求被拦截的情况,那么需要在页面(form表单)中添加上:
{% csrf_token %}
这个标签同样适用于ajax
的跨站请求中。
模板继承
通常,多个页面中,有些内容是一样的,比如页面底部的版本信息区。这些一般不变,但是每个页面都要有,为了避免开发中重复造轮子的,Django提供了模板继承机制,通常是定义一个基础页面,在这个页面中定义公共组件,别的页面如果有需要这些组件(块)的地方,直接继承这个页面即可,也可以针对某些组件进行修改。
模板继承语法:
-
模板:可以直接定义各页面的公共部分。
-
子页面:使用
{% extends 'base.html' %}
语法,继承指定的模板的公共部分。
首先我们在模版(base.html)中,定义好相关的组件,比如导航条和底部的版本信息区。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
{% block css %}
<style>
.w{
width: 100%;
height: 30px;
background-color: #ce8483;
text-align: center;
}
</style>
{% endblock %}
</head>
<body>
<!-- head start-->
{% block head %}
<div class="w"> 我是导行条</div>
{% endblock %}
<!-- head end-->
<!--content start-->
{% block content %} <!-- 预留的钩子,方便子页面扩展该部分 -->
<div class="w" style="margin-top: 300px;">我是内容</div>
{% endblock %}
<!--content end-->
<!-- foot start-->
{% block foot %}
<div class="w" style="position: fixed;bottom: 0;left: 0">我是底部版本信息区</div>
{% endblock %}
<!-- foot end-->
</body>
{% block js %}
{% endblock %}
</html>
上例为我们写好的模板组件文件base.html
。每个组件都使用{% block content %}
语法包裹公共组件代码。如果子页面不修改其中的内容,就意味着使用模板中的格式。除此之外,也通常预留css和js钩子。
其他页面直接继承即可,比如有一个sun.html
页面:
{% extends 'base.html' %}
{% block content %}
<h1>我继承了模版中的头部和足部组件,但重写了content组件</h1>
{% endblock %}
上例中的页面,因为头部和足部与模板一致,无需修改,但content部分需要自定制,所以,直接使用{% block content %}
语法, 重新定义该部分内容。
同样的,有时候,我们除了需要继续使用模板的内容,还需要添加上自己的内容,就需要用block.super了:
{% extends 'base.html' %}
{% block content %}
{{ bloc.super }}
<h1>我继承了模板中的头部和足部组件,但重写了content组件</h1>
{% endblock %}
另外,有时候可能会盒子套盒子,我们写了很多的block块,也对应的会写很多的endblock,那么会造成endblock对不上开头的block:
{% block content1 %}
{% block content2 %}
{% block content3 %}
{% endblock %}
{% endblock %}
{% endblock %}
正如上例所示,如果对不齐的话, 就会造成endblock混乱,所以,我们不仅可以在block中加上名字,也可以再endblock上加上名字:
{% block content1 %}
{% block content2 %}
{% block content3 %}
{% endblock content3 %}
{% endblock content2 %}
{% endblock content1 %}
这样,首尾呼应,比较好。
静态文件引入
参考:https://www.cnblogs.com/Neeo/articles/10835891.html
自定义标签
自定义标签常应用于非前后端分离项目中。
simple_tag
和自定义过滤器的套路一样,自定义标签的创建和使用步骤如下:
- settings.py中的
INSTALLED_APPS
列表中,配置app,不然Django无法找到自定义的simple_tag。 - 在app中创建
templatetags
目录,注意,目录名只能叫做templatetags
。 templatetags
目录内,创建任意py文件,比如mytag.py
,然后写你自定义的逻辑。- 前端页面引入。注意,与自定义过滤器不同的是,前端使用
{%%}
引入。
views.py
:
from django.shortcuts import render, HttpResponse, redirect
def books(request):
name = '张开'
return render(request, 'books.html', {"name": name})
mytag.py
:
from django import template
register = template.Library() # 注册器,变量名必须叫做register
@register.simple_tag # 被装饰的foo就成了Django的自定义标签了
def foo(v1):
return v1
@register.simple_tag # 被装饰的bar就成了Django的自定义标签了
def bar(v1, v2, v3): # 自定义标签参数个数不限
# 写你的逻辑
return v1 + v2 + v3
books.html
:
<!DOCTYPE html>
<html lang="en">
<head>
{% load mytag %} <!-- 需要load引入自定义标签所在的文件 -->
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{% foo 'v1' %}</div>
<div>{% bar 'v1' 'v2' 'v3' %}</div> <!-- 多个参数以空格分割 -->
</body>
</html>
inclusion_tag
inclusion_tag
的应用场景:通常适用于根据不同的身份,在同一套模板中,渲染不同的数据,也就是适用于动态的组件中。
和simpe tag的套路差不多,自定义标签的创建和使用步骤如下:
- settings.py中的
INSTALLED_APPS
列表中,配置app,不然Django无法找到自定义的simple_tag。 - 在app中创建
templatetags
目录,注意,目录名只能叫做templatetags
。 templatetags
目录内,创建任意py文件,比如mytag.py
,然后写你自定义的逻辑。- 前端页面引入。注意,前端使用
{%%}
引入。
templates/menu.html
:
<div>
<ul>
{% for foo in menu %}
<li>{{ foo }}</li>
{% endfor %}
</ul>
</div>
mytag.py
:
from django import template
register = template.Library() # 注册器,变量名必须叫做register
@register.inclusion_tag("menu.html") # 你要将哪个组件设置为动态组件
def dynamic_inclusion_tag(v1): # 自定义标签参数个数不限
return {"menu": v1} # 必须返回字典
views.py
:
from django.shortcuts import render, HttpResponse, redirect
def books(request):
# menu菜单中的数据应该是从数据库中取出来的,根据各种条件动态返回
# menu = ['后台管理', '主机管理', '数据统计']
menu = ['主机管理', '数据统计']
return render(request, 'books.html', {"menu": menu})
books.html
:
<!DOCTYPE html>
<html lang="en">
<head>
{% load mytag %} <!-- 需要load引入动态标签所在的文件 -->
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% dynamic_inclusion_tag menu %} <!-- 多个参数以空格分割 -->
</body>
</html>
流程:
- 浏览器访问books路由,Django的
books
视图函数中render了books.html
页面,并且传递menu
参数。 - 在
books.html
中声明({% load mytag %}
)了动态标签的文件,然后引用了该文件中的dynamic_inclusion_tag
函数,该函数接受一个参数,这个参数是上一步中传递的menu
列表。 dynamic_inclusion_tag
函数通过装饰器声明menu.html
为组件,并且传递menu
列表到组件中。menu.html
组件根据传过来的参数进行循环生成标签。- 在
books.html
中再(隐式的)引入menu.html
组件,最终返回给前端,进行展示。
注意事项
- Django模板语言不支持连续判断:
{% if a > b > c %}
...
{% endif %}
- Django模板语言中属性的优先级大于方法
# views.py
from django.shortcuts import render, HttpResponse, redirect
def books(request):
dict_obj = {"items": ['a', 'b']} # 会因为items导致前端报错
return render(request, 'books.html', {"dict_obj": dict_obj})
# books.html
"""
dict_obj.items原本是字典的方法,但由于后端dict_obj中有key叫做items,
所以模板语法会优先使用这个属性,所以前端报错: Need 2 values to unpack in for loop; got 1.
"""
{% for k, v in dict_obj.items %}
<p>{{ k }}----{{ v }}</p>
{% endfor %}
that's all,see also:
Django基础(16): 模板标签(tags)的介绍及如何自定义模板标签 | Django之模板 | Django官网