Django-3:django视图层与模板层
视图层
根据第一章第7节的django请求声明周期流程图来进行理解,先了解如何使用,能看到效果之后,后面再深究。
请求来了之后,先经过urls.py的路由匹配,然后再执行处理返回值的代码,这段代码称做视图类/视图函数,所在的层级在django中叫做视图层。
一、“三板斧”
回顾:
"""
HttpResponse
返回字符串类型
render
返回html页面 并且在返回给浏览器之前还可以给html文件传值
redirect
重定向
"""
注意:
-
django视图函数,比如要返回一个HttpResponse对象。
-
而render和redirect,都是继承了HttpResponse类,那么换句话说就是,render和redirect就是HttpResponse对象。
可根据查看源码得出结论。
否则报错:
The view 应用名.views.视图函数 didn't return an HttpResponse object. It returned None instead.
二、request对象方法
视图层函数中的request参数:
#如:
from django.shortcuts import render
def index(request):
# render需要两个参数,一个是request,另外一个是template_name
return render(request, 'index.html')
-
request:为请求相关的数据对象,里面有很多方法。
本章节作为基础阶段初次了解,更多方法在后面几期提及。
一、获取请求的方式
格式:
-
request.method
返回值:
- 返回值为请求的方式,并且是全大写的字符串。
示例代码:
-
''' views.py ''' from django.shortcuts import render,HttpResponse def login(request): print(request.method) return render(request,'login.html') ''' request.method 返回请求的方式,并且是全大写的字符串,测试结果如下图。 '''
![image-20210923003754187](https://s2.loli.net/2023/02/18/w3jxgYtZiP4OeW5.png)
主要应用:
- 利用 request.method可实现针对不同请求做不同的事情。
'''
当用户为get请求时,返回login.html
当用户为post请求时,返回字符串“提交成功”
'''
def login(request):
if request.method == 'POST':
return HttpResponse('提交成功')
return render(request,'login.html')
二、获取POST提交的普通数据
获取用户post请求提交的普通数据呢(非文件类型,该类型后续介绍):
request.POST
打印结果示例:
'''
<QueryDict: {
'Username': ['liuyu'],
'Password': ['123'],
'Gender': ['boy'],
'Birthday': ['2021-09-24'],
'Area': ['黄埔区'],
'Hobby': ['Eat', 'Sleep', 'Games']
}>
'''
request.POST将用户post提交的数据封装成一个querydict对象(字典格式,value为列表)
querydict对象两个获取数据的方法
request.POST.get(key) # 只获取列表最后一个元素,比如get('Hobby'),那么打印的结果就为'Games' request.POST.getlist() # 直接将列表取出
二、获取用户get请求提交的
-
获取用户get请求提交的
request.GET
测试时记得把form表单中的method属性改成get
打印结果与POST是一样的,只不过get请求时在浏览器的URL上,如:
http://localhost:8000/reg/?Username=liuyu&Password=123&Gender=boy&Hobby=Gamesrequest.GET返回的同样是querydict对象,也有get和getlist方法
request.GET.get() # 只获取列表最后一个元素 request.GET.getlist() # 直接将列表取出 """ 注: get请求携带的数据是有大小限制的,而post请求则没有限制。 """
补充:还有其他常用的方法
request.body
request.path # 或 request.path_info 实现的效果都是一样的
request.get_full_path()
- request.body 原生的浏览器发过来的二进制数据 ,也包括前端发来的JSON数据
- request.path 以及 request.path_info ,获取url中路径信息。
- request.get_full_path() 获取完整的url路径,及问号后面的参数 ,如:/forms_test/?next=123
2.1 前端标签注意事项
HTML文件中表单标签要注意添加name属性和value属性
-
表单标签的name属性,决定了QueryDict内每个元素的key是多少。
-
表单标签中,复选框需要添加value属性,否则django将无法收到value
复选框:
-
select标签(多选)
-
input表的CheckBox类型、radio类型(多选和单选)
-
前端页面配置好name和value属性后,表单提交后django可以收到的数据为:
![image-20210924151206514](https://s2.loli.net/2023/02/18/YhG91c7wUZB4oVe.png)
<QueryDict: {'Username': ['liuyu'], 'Password': ['123'], 'Gender': ['boy'], 'Birthday': ['2021-09-24'], 'Area': ['黄埔区'], 'Hobby': ['Eat', 'Sleep', 'Games']}>
2.2 post请求时,报错403
在提交post请求会遇到下列情况:
原因:
- django中间件会在请求来和走的时候,进行校验。
- 报403是因为不符合CSRF跨域校验
解决办法:
-
第一种,暂时将校验CSRF的中间件注释
'''settings.py''' MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
-
第二种,form表单符合校验
<form class="form-horizontal" action="" method="post"> <!-- 在form表单下面添加这么一行即可 --> {% csrf_token %} </form>
详情见django-中间件,再次之前可以暂时跳过。
三、JsonResponse对象
主要作用:
-
用于返回JSON格式的数据。
json格式数据的作用:
- 前后端数据交互,需要使用到json作为过渡,实现跨语言传输数据。
1.前端序列化---(JavaScript)
JSON.stringify()
JSON.parse()
2.后端序列化------(Python)
json.dumps()
json.loads()
本章节将从两个角度,详细的介绍JsonResponse对象的使用,以及不使用时,如何返回json格式的数据。
3.1 不使用的情况下
在不使用JsonResponse的情况下,返回给前端JSON格式的数据
import json
def json_test(request):
dic = {'name':'张三','title':'法外狂徒'}
dic_json = json.dumps(dic)
return HttpResponse(dic_json)
访问查看:
此时我们可以看到,中文数据自动转成了unicode。
如何解决上述转换成unicode码的问题?
给json.dumps传入参数 ensure_ascii=False
import json def json_test(request): dic = {'name':'张三','title':'法外狂徒'} # 转成json格式的时候,原封不动,不把中文转成unicode dic_json = json.dumps(dic,ensure_ascii=False) return HttpResponse(dic_json)
3.2 使用的情况下
如何使用:(基本的使用,后续还是需要再传入参数的,本段代码用于推导)
-
from django.http import JsonResponse def json_test(request): dic = {'name':'张三','title':'法外狂徒'} return JsonResponse(dic)
分析JsonResponse源码
class JsonResponse(HttpResponse):
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
'''
其他代码略
'''
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super(JsonResponse, self).__init__(content=data, **kwargs
- 首先继承了HttpResponse,说明可以直接return,然后根据第二行可以得出,JsonResponse其实还是用的json.dumps进行序列化,而源码中并未传入ensure_ascii=False,所以最终在浏览器展示的json数据,中文依旧是Unicode编码。
- 解决方法:分析源码中json.dumps的形参****json_dumps_params**,在倒数第五行中,也是if判断,通过这里我们可以知道,json_dumps_params在传进来的时候,应该是字典的格式。
- " ** "在调用dumps函数中是打散的作用,将传入的字典打散,并作为关键字参数,传入dumps函数中。
所以:
解决方案就很明显了,在使用JsonResponse时,传入json_dumps_params参数,值为字典格式的ensure_ascii=False即可。
from django.http import JsonResponse
def json_test(request):
dic = {'name':'张三','title':'法外狂徒'}
return JsonResponse(dic,json_dumps_params={'ensure_ascii':False})
注意:
-
默认只能序列化字典,序列化列表会报以下错:
In order to allow non-dict objects to be serialized set the safe parameter to False. ''' 翻译过来就是:为了允许序列化非 dict 对象,请将安全参数设置为 False。 '''
-
所以根据要求,那就再传一个参数。
from django.http import JsonResponse def json_test(request): lis = ['李四','法内狂徒'] return JsonResponse(lis,safe=False,json_dumps_params={'ensure_ascii':False})
四、form表单上传文件
在django中,普通文本数据会被request.POST所接收,但是一些文件,比如图片等,POST就无法接收了,就需要用到request.FILES
代码示例:
<form action="" method="post" enctype="multipart/form-data"> <p>上传图片:<input type="file" name="photo"></p> <p><input type="submit"></p> </form>
form表单上传文件类型的数据:
- 1.method必须指定成post
- 2.enctype必须换成formdata,否则只能接收到文件名。
视图层(路由层略)
def form_demo(request): if request.method == 'POST': # request.POST 只能拿到普通的键值对数据,文件不行 # request.FILES 专门获取文件数据 print(request.FILES) ''' <MultiValueDict: {'photo': [<InMemoryUploadedFile: Sh.jpg (image/jpeg)>]}> #photo对应前端name属性 ''' photo_obj = request.FILES.get('photo') # photo为前端name属性 #文件对象,get只取括号内该key的value最后一个值。与request.POST.get()相同 # 推荐保存的方式: # 文件对象.name 拿到的是上传文件的名称, 由于当前路径下没有该文件所以创建。 with open(photo_obj.name,'wb') as f: for line in photo_obj.chunks(): # 此处的chunks()可写可不写,因为写不写都是逐行读的,但是官方 推荐使用。 f.write(line) # 新建文件,名称为上传文件的名称,随后将数据一行一行以类似于拷贝的方式同步到本地。 return render(request,'form.html')
- request.FILES返回值为MultiValueDict对象,与Queryset对象大致相同,都是列表套字典的形式,还都有get方法来获取数据对象或文件对象。
- 获取到文件对象之后,可以通过创建空文件+写入文件数据的方式,将用户上传的文件保存在本地。
- 在保存时,官方推荐使用chunks()方法,但可用可不用,直接对文件对象进行迭代也是一样的。
五、FBV与CBV
上述案例代码中,写的都是基于函数的view,这种就叫做FBV。
把view写成基于类的,就叫做CBV
CBV示例:
-
路由层
urlpatterns = [ url(r'^cbv/',views.Cbv_test.as_view()), #views.类名.as_view() 这里必须要加括号 ]
-
视图层
from django.views import View # 导入 class Cbv_test(View): # 继承View def get(self,request): # 注意要传入request return render(request,'test.html') def post(self,request): # 注意要传入request return HttpResponse('post')
**CBV特点:**能够直接根据请求方式的不同直接匹配到对应的方法执行
-
模板层
<form action="" method="post"> {% csrf_token %} <button type="submit">提交</button> </form>
最终效果:
1.get请求可以获取到test.html页面。
2.该页面提交之后会展示字符串post。
需要整改的
声明时:
classmethod的第一个参数为类本身(cls),正如实例方法的第一个参数为对象本身(self);
staticmethod第一个参数不需要传入cls或self,故staticmethod中是无法访问类和对象的数据的。
调用时:都可用类名直接调用
也可用实例对象调用(不推荐,没必要)
1
5.1 CBV源码剖析
本章节将一步步详细的推导出CBV的实现原理
5.1.1 一
寻找突破口,注意观察路由
url(r'^cbv/',views.Cbv_test.as_view()),
# 这里的Cbv_test为视图层的类
发现:
-
类调用了as_view()方法,说明该方法不是@staicmethod修饰的静态方法就是被@classmethod修饰的类方法,并且还是加了括号直接运行的。
-
查看源码可得,这是个闭包函数。(下图为源码)
-
所以,as_view方法的返回值,取决于内部view函数,所以再去研究下view函数的源码。
此时:
url(r'^cbv/',views.Cbv_test.as_view()), # 就相当于 url(r'^cbv/',views.view),
那么这个时候就会发现,CBV和FBV,本质上都是一样的。
5.1.2 二
突破口已经打开,开始研究view函数
源码如下:
![image-20220907171555589](https://s2.loli.net/2023/02/18/TVaId63htZQ5qEY.png)
首先,需要关注下这一行,先明确这个cls和self是什么?
self = cls(**initkwargs)
-
由于as_view函数是被@classmethod修饰的类方法,由类调用,并将类自动传入,而view函数又是as_view函数内部的闭包函数,可以访问as_view名称空间,所以这个cls就是我们视图层的那个类本身。
# 此时,self就变成了 self = 视图函数类(**initkwargs) # 如: self = Cbv_test(**initkwargs)
-
所以,这个self就是我们创建视图类,所实例化的对象。
再来关注下这一行
return self.dispatch()
根据对象属性查找顺序查找下dispatch方法
-
1.先从对象自己找 。
-
2.从类中找。
-
3.从父类中找。
很明显,最终在父类中才能找到dispatch方法,所以后续要再研究dispath方法的源码。
5.1.3 三
dispatch源码:(CBV的核心)
首先要明确,该方法的self是谁?
- 是我们视图类,所实例化的对象。
来看这一行
if request.method.lower() in self.http_method_names:
-
request.method.lower() 这个好理解,访问的方式,由于返回的是大写的字符串格式,所以再加上lower方法变成小写。
-
self.http_method_names ? 这个是什么
由于该属性对象本身没有,类中也没有,最后在继承的类中,找到了该属性
http_method_names为列表,其实就是几种HTTP请求方式。
所以,if判断的这行代码,其实就是在做HTTP请求类型的校验
回到源码,接着往下分析
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
第二行分析:
- 首先,当访问的HTTP请求类型正常时,就去判断我们由类,示例出来的对象,有没有属性或方法名,等于这个请求方式名的。 如:请求方式是get,那么就去利用反射,查询对象是否具有get属性或方法,如果没有,就返回后面自定报错, 如果有,那么就返回属性的值,或方法的内存地址。
return分析:
-
此时的handler,就等于我们类中的get或者post方法的内存地址,至于是get还是post,还是其他的,取决访问的HTTP请求格式。
-
后续加括号调用,就完成了根据不同请求,执行不同的视图函数。
模板层
六、模版语法传值
模版语法的作用:
- 可以传递的后端python数据类型
格式:
-
{{}}:变量相关
如:
{{ list }}
-
{%%}:逻辑相关
{% if i in user_dict %} {% endif %}
代码示例
后端(python):
dic = {'name':'刘','age':'21'}
lis = [1,2,3]
tup = (2,2,2)
set = {1,2,3}
# return render(request,'index.html',{}) # 一个个传
return render(request,'index.html',locals())
前端:
<p>{{ dic }}</p>
<p>{{ lis }}</p>
<p>{{ tup }}</p>
<p>{{ set }}</p>
注意事项:
'''
模板语法也可以传入函数、类和对象。
1.当传入函数时,会自动加括号执行,但不可以传参数。
2.当传入类名时,也会加括号实例化
3.当传入对象或对象.方式时,模板语法内部能够自动判断出当前的变量名是否可以加括号调用,如果可以就会自动执行。
3.1 当对象具有__str__方法或__repr__方法时,模板语法会打印对象,从而执行内部的代码。 (__str__:打印对象时执行)
3.2 当对象具有__call__方法时,模板语法会加括号执行。 (__call__: 当对象加括号被执行时,执行。)
4.需要让前端接收数据时,应该有返回值,不然会None
'''
<p>{{ func }}</p> # 传递函数名会自动加括号调用 但是模版语法不支持给函数传额外的参数:
<p>{{ MyClass }}</p> # 传类名的时候也会自动加括号调用(实例化)
# django模板语法,内部能够自动判断出当前的变量名是否可以加括号调用,如果可以就会自动执行。
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p>
另外,django模版语法的取值 是固定的格式 只能采用“句点符” .
'''
也就是说,当前端需要获取列表某个元素、字典某个k的v,都可以使用“.”
'''
lis[0] ---> lis.0
dic['name'] ---> dic.name
dic['addr'][3] ---> dic.addr.3
# 在python中利用[]或其他方式取值,到了模板语法中,都要变成.的形式。
'''可以混用'''
<p>{{ d.hobby.3.info }}</p> # 字典d中,在key为hobby的value中,取索引为3且key为info的值(value)
七、模板语法-过滤器
作用:
- 类似于python的内置函数,可以对传给Html的数据进行**“二次处理”**,比如统计长度等。
格式:
{{ 变量名 | 过滤器:可选参数 }}
过滤器有六十多个,本章节暂时只介绍部分。
汇总一览:
过滤器名称 | 作用 |
---|---|
length | 统计长度 |
default | 当数据的布尔值为False时,返回自定义的默认值,反之则返回数据本身的值 |
filesizeformat | 文件大小单位换算,自动算出为多少KB、MB、GB等。 |
date | 日期格式化 |
slice | 切片操作(支持步长) |
truncatechars | 切取字符 |
truncatewords | 按照空格切来切取字符 |
cut | 移除特定的字符 |
join | 字符串拼接操作(列表转字符串) |
add | 字符串的拼接,或相加。 |
safe | 转义(后端使用mark_safe之后,就不需要在前端转义了) |
length
-
作用: 统计长度
# 路由: url(r'test/',views.test), # 视图: def test(request,*args): str1 = 'liuyu' age = 22 msg = {'school':'中国地质大学','college':'信息工程学院'} return render(request,'test.html',locals()) # 模板 {{ str1|length }} # 结果:5 {{ age|length }} # 结果:0 {{ msg|length }} # 结果:2 ''' 数字无法统计长度,所以结果为0 字典统计有多少key,所以结果为2 '''
default
-
作用:当数据的布尔值为False时,返回自定义的默认值,反之则返回数据本身的值
# 视图 is_del1 = True is_del12 = False return render(request,'test.html',locals()) # 模板 <p> {{ is_del1|default:'已删除' }} #结果: True </p> <p> {{ is_del2|default:'已删除' }} #结果: 已删除 </p> ''' 当变量名的值,转换为bool类型为False时,返回自定义值。 '''
filesizeformat
-
作用:单位换算,自动算出为多少KB、MB、GB,如:2048=2.0KB。
# 视图(多余代码略) size = 10012300 # 模板 {{ size|filesizeformat }} # 结果:9.5 MB
date
-
作用:用于格式化时间
# 视图(多余代码略) import datetime current_time = datetime.datetime.now() # 结果:2021-11-03 22:11:16 # 模板 {{ current_time }} # 结果:Nov. 3, 2021, 10:40 p.m. {{ current_time|date:'Y-m-d H:m:s' }} # 结果:2021-11-03 22:11:16 ''' python后端的时间格式为: 2021-11-03 22:11:16 直接在前端使用: Nov. 3, 2021, 10:40 p.m. 所以为了时间看起来更直观,最好使用data过滤器,将事件进行格式化。 '''
slice
-
作用:切片,可支持步长
# 视图(多余代码略) lis = [1,2,3,4,5] # 模板 {{ lis|slice:'0:4:2' }} # 结果:[1,3] ''' 取索引0到4 即:[1,2,3,4] 然后取每隔2-1位的值,即:[1,3] '''
truncatechars
-
作用:取x个字符
# 视图(多余代码略) str1 = '又是元气满满的一天' # 模板 {{ str1|truncatechars:4 }} # 结果:又... ''' 该方法会强制的加上三个点'...' 所以,当{{ str1|truncatechars:1 }}时,结果:... 当{{ str1|truncatechars:4 }}时,结果:又... '''
truncatewords
-
作用:按照空格切片(该方法不会强制加上三个点’…')
# 视图(多余代码略) str1 = '又 是 元 气 满 满 的 一 天' # 模板 {{ str1|truncatewords:4 }} # 结果:又 是 元 气 ...
cut
-
作用:移除特定的字符
# 视图(多余代码略) str1 = '又 是 元 气 满 满 的 一 天' str2 = '1-2-3' # 模板 <p>{{ str1|cut:' ' }}</p> # 结果:又是元气满满的一天 <p>{{ str2|cut:'-' }}</p> # 结果:123
join
-
作用:列表转字符串,拼接
# 视图(多余代码略) lis = ['A','B','C'] # 模板 {{ lis|join:'-' }} # 结果:A-B-C
add
-
作用:字符串相加或者拼接
# 视图(多余代码略) str1 = '123' str2 = 'abcd' # 模板 <p>{{ str1|add:'456' }}</p> # 结果:579 <p>{{ str2|add:'efg' }}</p> # 结果:abcdefg ''' 当变量的值,和add参数的值,都是数字组成时,add表示相加运算 反之则是字符串拼接。 '''
safe
-
作用:转义,将字符串中的标签变得有意义
# 视图(多余代码略) info = '<h1>转义</h1>' # 模板 {{ info|safe }} # 结果: h1标题样式的 转义 二字 ''' 在不使用safe时,前端会原封不动的将字符串中的数据展示在页面上,HTML格式的字符串,并不会被当做代码去渲染在页面上。 '''
扩展: python如何实现在后端完成转义
# 视图 def test(request,*args): from django.utils.safestring import mark_safe res = mark_safe('<h1>标题</h1>') return render(request,'test.html',locals()) # 模板 {{ res }} ''' python完成了转义的工作,到了HTML页面,就不需要再使用safe过滤器了。 '''
7.1 自定义过滤器
分三步走:
-
第一:在应用下创建一个名字”必须“叫templatetags文件夹,具体是app01还是02,要看是哪个应用的视图做处理
-
在该文件夹内创建“任意”名称的py文件,如;myfilter.py、mytag.py等
-
在该py文件内"必须"先书写下面两句话(单词一个都不能错)
from django import template register = template.Library() # 注意加括号
![image-20211108163857721](https://s2.loli.net/2023/02/18/UsnkKhtvx2X6DP5.png)
代码示例:
'''
templatetags/mytag.py (该py文件的名称可自定义)
'''
from django import template
register = template.Library() # 注意加括号
# tag1_test为自定义过滤器的名字,后续调用使用的就是该名字
@register.filter(name='tag1_test')
def my_count(v1, v2): # 函数名my_count可任意取
return v1 + v2
# tag2_test作为过滤器的唯一标识
@register.filter(name='tag2_test')
def my_count(v1, v2): # 函数名重复也无所谓的,因为调用时作为唯一标识的不是它,而是上面指定的过滤器名字
return v1 - v2
'''
views.py
'''
def test(request):
int1 = 998
int2 = 1999
return render(request,'test.html',locals())
'''
test.html
'''
{# 自定义过滤器 #}
{% load mytag %} # 引入,mytag为templatetags文件夹下的py文件。
<p>{{ int1|myfilter1:int2 }}</p>
<p>{{ int1|myfilter2:int2 }}</p>
'''
int1,会被过滤器的第一个形参所接收,所以{{ int1|myfilter1:int2 }} = int1+int2
过滤器也就只支持两个参数。
'''
所以最终实现的效果应该是,int1+int2 以及 int1-int2,浏览器展示:
八、模板语法-标签
普通for循环
<ul>
{% for user in user_list %} <!--直接打for,基本的格式会tab补全出来-->
<li>{{ user.name }}</li>
{% endfor %}
</ul>
forloop
观察下列代码的结果
# 视图 def test(request,*args): user_list = ['张三','李四','王五','马六','侯七'] return render(request,'test.html',locals()) # 模板 <ul> {% for user in user_list %} <li> {{ forloop }} </li> {% endfor %} </ul> # 输出结果: ''' {'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} {'parentloop': {}, 'counter0': 2, 'counter': 3, 'revcounter': 3, 'revcounter0': 2, 'first': False, 'last': False} {'parentloop': {}, 'counter0': 3, 'counter': 4, 'revcounter': 2, 'revcounter0': 1, 'first': False, 'last': False} {'parentloop': {}, 'counter0': 4, 'counter': 5, 'revcounter': 1, 'revcounter0': 0, 'first': False, 'last': True} '''
对应关系如下表:
forloop.counter | 当前循环的索引值(从1开始) |
---|---|
forloop.counter0 | 当前循环的索引值(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(从1开始) |
forloop.revcounter0 | 当前循环的倒序索引值(从0开始) |
forloop.first | 当前循环是不是第一次循环(布尔值) |
forloop.last | 当前循环是不是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
forloop可以结合if判断,比如是否是第一次循环,是否为第最后一次循环,等。
if判断
{% if 条件 %}
<p>XXX</p>
{% elif 条件 %}
<p>XXX</p>
{% else %}
<p>XXX</p>
{% endif %}
for与if可混合使用
{% for name in lis %}
# 第一次循环时执行
{% if forloop.first %}
<p>第一位同学为:{{ name }}</p>
# 最后一次循环时执行
{% elif forloop.last %}
<p>最后一位同学为:{{ name }}</p>
{% else %}
<p>{{ name }}</p>
{% endif %}
{% endfor %}
模板语法处理字典的方法
-
迭代取出字典的key
<ul> # {% for user in user_dict.keys %} {% for user in user_dict %} <li>{{ user }}</li> {% endfor %} </ul> ''' 因为默认就是key,所以可以不在.keys '''
-
迭代取出字典的value
<ul> {% for user in user_dict.values %} <li>{{ user }}</li> {% endfor %} </ul>
-
迭代取出字典的key和value
<ul> {% for user in user_dict.items %} <li>{{ user }}</li> {% endfor %} </ul> ''' 元祖格式,例如: ('name', 'liuyu') ('age', 22) '''
8.1 自定义标签
作用:
- 类似于函数,后端定义之后,前端传入参数调用。
- 相比过滤器来说,自定义标签是可以像函数一样,传入多个参数的。
格式:
{% 自定义标签名 '参数1' '参数2' '参数3' '参数4' ...... '参数x' %}
使用:
后端定义
# templatetags/mytag.py文件 @register.simple_tag(name='tag1_test') def my_tag(name,year,month,day): return f'姓名:{name},生日:{year}-{month}-{day}'
前端引入使用
# 前端html文件 {% load mytag %} <p>{% tag1_test 'liuyu' '2000' '7' '19' %} </p>
类似于linux系统敲命令一样,直接输入命令(函数)+参数,如 ls /home
九、自定义inclusion_tag
作用:
- 当html某一个地方的页面,需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部,那么就考虑将该局部页面做成inclusion_tag形式
代码示例:
定义inclusion_tag
from django import template register = template.Library() @register.inclusion_tag('menu.html') def menu(num): data = [f'第{i}项' for i in range(1,num+1)] # 将值,传给menu.html return locals()
index页面
# mytag为自定义模板语法文件的文件名 {% load mytag %} # 调用menu函数,传入值为10 {% menu 10 %}
menu页面
<ul> {% for i in data %} <li>{{ i }}</li> {% endfor %} </ul>
流程:
- 浏览器访问index页面,经过路由层、视图层将Index.html返回。
- 在index页面中,引入了mytag,并且后续的{% menu 10 %},调用了模板语法中自定义menu函数,并且传入参数 10。
- mytag.py文件,执行menu函数,利用index.html中传入的10,做一些处理,最后将数据传值到menu.html上
- menu.html获取到数据后完成渲染,但,是渲染在了调用它的位置,也就是index.html上
''' 所以最后的效果就是: index.html中{% menu 值 %},后面的参数值为多少,最后展示在页面上的li标签就有多少。 '''
十、总结
@register.filter(name='xxx') # 自定义过滤器
@register.simple_tag(name='xxx') # 自定义标签
@register.inclusion_tag('xxx.html') # 自定义inclusion_tag
'''
在使用方式上:
'''
1.自定义过滤器:
{% load xxx %} # 先引入
{{ a|xxx:b }} # a和b将作为参数传入到自定义过滤器函数的参数。
2.自定义标签
{% load xxx %} # 名称根据实际情况来
{% xxx '参数1' '参数2' '参数3' %}
3.自定义inclusion_tag
定义函数,然后在模板语法中传入参数使用
十一、模版的继承
作用:
- 可以实现页面的局部变化,如点击导航条,只有面板区域进行变化等。
使用方法
'''
子页面继承主页面
'''
{% extends 'home.html' %} #home.html为主页面,该行表示子页面继承主页面,继承之后与主页面一模一样。
由于继承了之后,子页面跟模版页面(主页面)长的是一模一样的,所以需要在主页面上提前划定可以被修改的区域
'''
主页面
'''
{% block content %}
模版HTML代码
<h1>主页面</h1>
{% endblock %}
# 这里的content代表这一块区域的HTML代码,后续子页面可以对其进行自定义修改。
'''
子页面:
修改模板指定区域的代码
'''
{% block content %}
子页面内容
<h1>子页面</h1>
{% endblock %}
子页面和主页面都书写完毕之后,当我们触发请求,执行视图函数并返回子页面时,不会再重新新建标签页,而是在主页面中提前划定好的区域做页面内容展示区域。
案例:
- 页面登陆与注册局部刷新。
一、布局如下:
![image-20211112174933198](https://s2.loli.net/2023/02/18/qOHnBeNTbck9tW1.png)
二、在主页面上划定区域
- 划定两个区域(其实一个区域就够了,但为了加深印象,此处划了2个)
![image-20211112175646327](https://s2.loli.net/2023/02/18/6jgAGOTdvSrB7JI.png)
三、子页面继承,并修改区域独有的代码
'''
子页面 login.html
'''
{% extends 'home.html' %} # 继承主页面
{% block title %} # 修改主页面划定的区域代码。
<h1>登陆</h1>
{% endblock %}
# 修改主页面中的标签区域代码,换成表单。
{% block input %}
<form class="form-horizontal" action="" method="post">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Uname</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputEmail3" placeholder="Uname" name="username" >
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password" name="password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success ">Sign in</button>
</div>
</div>
</form>
{% endblock %}
![image-20211112180218824](https://s2.loli.net/2023/02/18/msxGptODw5krfQ3.png)
- 在主页面中,左侧面板的两个Li标签,也就是登陆和注册按钮,都是a链接标签,所以点击之后会朝后端发送请求,路由通过之后,执行视图函数,此时只要视图函数返回的HTML页面就是“子页面”时,则会在原来划定的区域内展示页面,如上图所示。
11.1 其他
一般情况下模版页面上应该至少有三块可以被修改的区域
- css区域
- html区域
- js区域
{% block css %}
{% endblock %}
{% block content %}
{% endblock %}
{% block js %}
{% endblock %}
# 每一个子页面就都可以有自己独有的css代码 html代码 js代码
"""
一般情况下 模版的页面上划定的区域越多 那么该模版的扩展性就越高
但是如果太多 那还不如自己直接写
"""
十一、模版的导入
"""
将页面的某一个局部当成模块的形式
哪个地方需要就可以直接导入使用即可
"""
{% include 'xxxx.html' %}
- 导入模板(HTML)之后,会在导入的地方,插入模板内的所有代码。