Django之视图层

img

django的视图主要有2种,分别是函数视图类视图

Django的视图层默认定义在views.py文件中,用来处理web请求信息和返回信息的函数:请求对象(HttpRequest)和相应对象(HttpResponse)

官网解释:请求和响应对象

一、请求与相应

请求对象(HttpReques)

​ 当一个页面被请求时,django会创建一个包含本次请求原信息(如http协议请求报文中的请求行、首部信息、内容主体)的HttpRequest对象。

​ 之后,django会找到匹配的视图,将该对象传给视图函数的第一个参数,约定俗称该参数名为request。

​ 在视图函数中,通过访问该对象的属性便可以提取http协议的请求数据

常用的http请求:

POST 添加/上传

GET 获取/下载

PUT/PATCH 修改,其中PUT表示修改整体数据,PATCH表示修改部分数据

DELETE 删除

HttpRequest对象常用属性part1

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
33
34
request本质就是WSGIRequest的实例对象,父类是from django.http.request import HttpRequest
一.HttpRequest.method
  获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET""POST"
应该通过该属性的值来判断请求方法

在视图函数中:
if request.method == "GET":
...
if request.method == "POST":
...

二.HttpRequest.GET
  值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值
http://127.0.0.1:8000/app01/login?name=allen&passwd=123
<QueryDict: {'name': ['allen'], 'passwd': ['123']}>
QueryDict 是一个经过封装的字典,提供了基本的字典操作,还提供额外的操作给开发者获取字典内部的数据,因为python基本数据类型dict是无法让字典在一个key下指向多个值的情况,所以Django封装了一个新的字典来实现

在视图函数中:
request.GET.get('name')

三.HttpRequest.POST
值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值

在视图函数中:
request.POST.get('name')

针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")
针对有多个值的的情况,也可以用HttpRequest.GET.get("键"),会获取列表中的最后一个值
四. 比如
request.method 获取请求动作
request.path 获取请求地址
request.path_info 获取请求地址信息
request.get_port 获取端口

案例:

1
2
3
4
5
6
from django.urls import re_path
from app01 import views

urlpatterns = [
re_path('login/',views.login),
]
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
from django.shortcuts import render,HttpResponse

def login(request):
if request.method == 'GET':
# 当请求url为:http://127.0.0.1:8000/login/?a=1&b=2&c=3&c=4&c=5
# 请求方法是GET,?后的请求参数都存放于request.GET中
print(request.GET)
# 输出<QueryDict: {'a': ['1'], 'b': ['2'], 'c': ['3', '4', '5']}>

# 获取?后参数的方式为
a=request.GET.get('a') # 1
b=request.GET.get('b') # 2
c=request.GET.getlist('c') # ['3', '4', '5']
c1=request.GET.get('c') # 5
c2=request.GET.dict() # 转换成字典,会去重{'a': '1', 'b': '2', 'c': '5'}

return render(request,'login.html')
elif request.method == 'POST':
# 在输入框内输入用户名allen、年龄18,选择爱好,点击提交
# 请求方法为POST,表单内的数据都会存放于request.POST中
print(request.POST)
# 输出<QueryDict: {..., 'name': ['allen'], 'age': ['18'], 'hobbies': ['music', 'read']}>

# 获取表单中数据的方式为
name=request.POST.get('name') # allen
age=request.POST.get('age') # 18
hobbies=request.POST.getlist('hobbies') # ['music', 'read']

return HttpResponse('提交成功')
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>

<!--
method="post"代表在提交表单时会以POST方法提交表单数据
action="/login/" 代表表单数据的提交地址为http://127.0.0.1:8000/login/,可以简写为action="/login/",或者action=""
-->
<form action="http://127.0.0.1:8000/login/" method="post">
{% csrf_token %} <!--强调:必须加上这一行,后续我们会详细介绍-->
<p>用户名:<input type="text" name="name"></p>
<p>年龄:<input type="text" name="age"></p>
<p>
爱好:
<input type="checkbox" name="hobbies" value="music">音乐
<input type="checkbox" name="hobbies" value="read">阅读
<input type="checkbox" name="hobbies" value="dancing">跳舞
</p>
<p><input type="submit" value="提交"></p>

</form>
</body>
</html>

HttpRequest对象常用属性part2

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
一.HttpRequest.body
1.1 当浏览器基于http协议的GET方法提交数据时(没有请求体一说),数据会按照k1=v1&k2=v2&k3=v3的格式放到url中,然后发送给django,django会将这些数据封装到request.GET中,注意此时的请求体request.body为空、无用

1.2 当浏览器基于http协议的POST方法提交数据时,数据会被放到请求体中发送给django,django会将接收到的请求体数据存放于HttpRequest.body属性中.
但该属性的值为Bytes类型(套接字数据传输都是bytes类型),而通常情况下直接处理Bytes、并从中提取有用数据的操作是复杂而繁琐的,好在django会对它做进一步的处理与封装以便我们更为方便地提取数据,具体如何处理呢?

当前端采用POST提交数据时,数据有三种常用编码格式,编码格式不同Django会有不同的处理方式
# 编码格式1:application/x-www-form-urlencoded,是form表单默认编码格式
# 编码格式2:multipart/form-data,上传文件专用格式
# 编码格式2:application/json,提交json格式字符串

#====》I: 当POST数据的编码格式为application/x-www-form-urlencoded时《====
HttpRequest.body中的数据格式为b'a=1&b=2&c=3'
django会将其提取出来封装到request.POST中
request.FILES此时为空

如:
print(request.body) # b'a=1&b=2&c=3'
print(request.POST) # <QueryDict: {'a': ['1'],'b': ['2'],'c': ['3']}
print(request.FILES) # <MultiValueDict: {}>


#====》II: 当POST数据的编码格式为multipart/form-data时《====
详见:https://my.oschina.net/cnlw/blog/168466?fromerr=aQL9sTI2

HttpRequest.body中的数据格式为b'------WebKitFormBoundaryKtcwuksQltpNprep\r\nContent-Disposition: form-data;......',注意,文件部分数据是流数据,所以不在浏览器中显示是正常的
django会将request.body中的非文件数据部分提取出来封装到request.POST中
将上传的文件部分数据专门提取出来封装到request.FILES属性中

如:
print(request.body) # 不要打印它,打印则报错,因为它是数据流
print(request.POST) # <QueryDict: {'a': ['1'],'b': ['2'],'c': ['3']}
print(request.FILES) # <MultiValueDict: {'head_img': [<InMemoryUploadedFile: 11111.jpg (image/jpeg)>]}>

强调:
1、毫无疑问,编码格式2的数据量要大于编码格式1,如果无需上传文件,还是推荐使用更为精简的编码格式1
2、FILES will only contain data if the request method was POST and the <form> that posted to the request had enctype="multipart/form-data". Otherwise, FILES will be a blank dictionary-like object.

#===》III: 当POST数据的编码格式为application/json时《====
此时在django后台,request.POST和request.FILES中是没有值的,都放到request.body中了,需要用json.loads对其进行反序列化

如:
print(request.body) # b'{"a":1,"b":2,"c":3}'
print(request.POST) # <QueryDict: {}>
print(request.FILES) # <MultiValueDict: {}>


1.3 如何设定POST提交数据的编码格式
前端往后台POST提交数据,常用技术有form表单和ajax两种
form表单可以设置的数据编码格式有:编码格式1、编码格式2
ajax可以设置的数据编码格式有:编码格式1、编码格式2、编码格式3

我们将在后续章节中介绍ajax技术,此处我们先介绍我们熟知的form表单
form表单可以通过属性enctype进行设置编码格,如下
编码格式1(默认的编码格式):enctype="application/x-www-form-urlencoded"
编码格式2(使用form表单上传文件时只能用该编码):enctype="multipart/form-data"

案例:form表单上传文件

1
2
3
4
5
6
from django.urls import re_path
from app01 import views

urlpatterns = [
re_path('register/',views.register),
]
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from django.shortcuts import render,HttpResponse

def register(request):
'''
保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。
但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。
'''
if request.method == 'GET':
return render(request, 'register.html')
elif request.method == 'POST':
# print(request.body) # 读取流数据会抛出异常,请注释掉它

# 从request.POST中获取用户名
name = request.POST.get('name')

# 从request.FILES获取文件对象
file_obj = request.FILES.get('header_img')
# 从文件对象中获取文件名
filename = file_obj.name
print(filename)

file_obj.chunks()
# 上传的文件存放于templates文件夹下
with open(f"templates/{file_name}", 'wb') as f:
for line in file_obj.chunks():
f.write(line)

return HttpResponse('注册成功')


# 强调:
'''
file_obj.read() 当文件过大时,会导致内存溢出,系统崩溃
应该使用file_obj.chunks()读取,官网解释如下
uploadedFile.chunks(chunk_size=None)¶
A generator returning chunks of the file. If multiple_chunks() is True, you should use this method in a loop instead of read().

In practice, it’s often easiest simply to use chunks() all the time. Looping over chunks() instead of using read() ensures that large files don’t overwhelm your system’s memory.

注意:直接for循环file_obj也不行,因为直接for读取文件是按照行分隔符来依次读取的,不同平台下的文件行分隔符不同,有可能一行的数据就很大,所以还是推荐file_obj.chunks()来读取,官网解释如下
Like regular Python files, you can read the file line-by-line simply by iterating over the uploaded file:

for line in uploadedfile:
do_something_with(line)

Lines are split using universal newlines. The following are recognized as ending a line: the Unix end-of-line convention '\n', the Windows convention '\r\n', and the old Macintosh convention '\r'.
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>

<form action="" method="POST" enctype="multipart/form-data" >
{% csrf_token %}
<p>
用户名:<input type="text" name="name">
</p>
<p>
头像:<input type="file" name="header_img">
</p>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>

HttpRequest对象常用属性part3

1
2
3
4
5
6
7
8
9
一.HttpRequest.path
  获取url地址的路径部分,只包含路径部分

二.HttpRequest.get_full_path()
  获取url地址的完整path,既包含路径又包含参数部分

如果请求地址是http://127.0.0.1:8000/order/?name=allen&age=10#_label3,
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=allen&age=10"

案例:

1
2
3
4
5
6
from django.urls import path,register_converter,re_path
from app01 import views

urlpatterns = [
re_path(r'^order',views.order),
]
1
2
3
4
5
6
7
8
9
10
from django.shortcuts import render,HttpResponse

## 针对请求的url地址:http://127.0.0.1:8000/order/?name=allen&age=10#_label3
## 从域名后的最后一个“/”开始到“?”为止是路径部分,即/order/
## 从“?”开始到“#”为止之间的部分为参数部分,即name=allen&age=10
def order(request):
print(request.path) # 结果为“/order/”
print(request.get_full_path()) # 结果为"/order/?name=allen&age=10"

return HttpResponse('order page OK')

HttpRequest对象常用属性part4(暂作了解)

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""
  django将请求报文中的请求行、头部信息、内容主体封装成 HttpRequest 类中的属性。
除了特殊说明的之外,其他均为只读的。
"""
一.HttpRequest.META
  值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME类型。
HTTP_ACCEPT —— 响应可接收的Content-Type
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送数据的目标主机与端口
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端使用的软件版本信息
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET""POST"
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
  从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键时,
都会
1、将所有字母大写
2、将单词的连接符替换为下划线
3、加上前缀HTTP_。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。

注意:下述常用属性暂且了解即可,待我们讲到专门的知识点时再专门详细讲解
二.HttpRequest.COOKIES
  一个标准的Python 字典,包含所有的cookie。键和值都为字符串。

三.HttpRequest.session
 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。

11.HttpRequest.user(用户认证组件下使用)

  一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。

2.HttpRequest.is_ajax()

  如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'

  大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。

  如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,
你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。

四.HttpRequest.encoding
  一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。
这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。
接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。
如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。

五.HttpRequest.scheme
表示请求方案的字符串(通常为http或https)

六.HttpRequest.user
  一个AUTH_USER_MODEL类型的对象,表示当前登录的用户。

  如果用户当前没有登录,user将设置为django.contrib.auth.models.AnonymousUser的一个实例。
你可以通过is_authenticated()区分它们,例如:

if request.user.is_authenticated():
# Do something for logged-in users.
else:
# Do something for anonymous users.

HttpRequest.user只有当Django 启用 AuthenticationMiddleware中间件时才可用。



匿名用户
class models.AnonymousUser

django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:

id 永远为None
username 永远为空字符串。
get_username() 永远返回空字符串。
is_staff 和 is_superuser 永远为False
is_active 永远为 False
groups 和 user_permissions 永远为空。
is_anonymous() 返回True 而不是False
is_authenticated() 返回False 而不是True
set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
New in Django 1.8:
新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。

响应对象

请求对象HttpRequest是由django为我们创建好的,直接使用即可,而响应对象则需要我们负责创建。我们编写的每个视图都应该返回一个HttpResponse对象,响应可以是一个网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。用来返回响应对象的常用类如下

1
2
3
4
from django.shortcuts import HttpResponse
from django.shortcuts import redirect
from django.shortcuts import render
from django.http importJsonResponse

HttpResponse

传递字符串

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
from django.http import HttpResponse
response = HttpResponse("Here's the text of the Web page.")
response = HttpResponse("Text only, please.", content_type="text/plain")

'''
ps:Content-Type用于指定响应体的MIME类型

MIME类型:
mime类型是多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开


MIME 类型有非常多种,一般常见的有:

  text/html:浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理。

  text/plain:意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。

  image/jpeg:JPEG格式的图片

  image/gif:GIF格式的图片

  video/quicktime:Apple 的 QuickTime 电影

  application/vnd.ms-powerpoint:微软的powerpoint文件
'''

依次增加字符串(了解)

1
2
3
>>> response = HttpResponse()
>>> response.write("<p>Here's the text of the Web page.</p>")
>>> response.write("<p>Here's another paragraph.</p>")

传递迭代器对象(了解)

1
2
3
4
5
6
7
8
9
## 可以为HttpResponse传递可迭代对象,HttpRespone会将其依次迭代然后存储成字符串,类似文件或者其他带有close()方法的生成器对象,会自动调用close()关闭

def index(request):
f=open(r'a.txt',mode='r',encoding='utf-8') # a.txt文件内容为111
response=HttpResponse(f)
print(response.content) # 打印内容为: b'111'

# f.read() # 如果执行,则会抛出异常,说明文件已经关闭了,无法读取
return response

自定义添加或删除响应头信息

1
2
3
4
response = HttpResponse()
response['Content-Type'] = 'text/html; charset=UTF-8'
response['X-Frame-Options'] = 'SAMEORIGIN'
del response['Content-Type']

属性

1
2
3
4
5
HttpResponse.status_code:响应的状态码

HttpResponse.charset:响应内容的编码encode格式

HttpResponse.content:响应内容

返回图片信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def index(request):
'''相应内容'''
'''
通过django.http.response.HttpResponse响应html内容
return HttpResponse("<h1>HELLO</h1>")
content: 响应正文内容
status: 响应状态码
content-type: 响应内容的MIME类型
ret = HttpResponse(content='HELLO',status='206',content_type='text/html')
return ret
'''
# 返回其他格式内容如:图片信息,文件,资源下载。。。
content=open('qqq.png','rb').read()
return HttpResponse(content=content,status=206,content_type="image/png")

提供下载支持

1
2
3
4
def wget(request):
with open(r"C:\Users\User\Downloads\app.zip", 'rb') as f:
content=f.read()
return HttpResponse(content=content,content_type="application/x-zip-compressed")

字符串编码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 编码设定原则:编码格式与解码格式保持一致
response = HttpResponse(charset='gbk',content_type='text/html; charset=gbk')

## 参数charset='gbk'指定响应体内容response.content的编码格式
## 参数content_type='text/html; charset=gbk'是设置响应头,用于告诉浏览器响应体内容response.content应该采用何种解码格式,注意:设置时必须加上内容的类型,如text/html

## 强调:
## 如果在实例化HttpResponse时没有指定charset=gbk',将会采用与content_type中设定的解码格式一样的编码,这样统一起来就不会出现乱码问题
## 如果也没有指定content_type,那么django默认会读取配置文件中的配置settings.py.DEFAULT_CHARSET来作为默认的编码与解码格式

def index(request):
response = HttpResponse(charset='gbk',content_type='text/html; charset=gbk')
response.charset='gbk' # 实例化后,可以对该属性重新赋值,只要是在write内容前,就会以最新赋值的编码格式为准

response.write("<p>Hello allen美男子</p>")
response.write("<p>Hello 林sb</p>")

# 如果实例化处未指定响应头Content-Type,也可以在此处设置,在一处就可以了,无需重复设置
response['Content-Type']='text/html; charset=gbk'
# response['Content-Type']='text/html; charset=UTF-8'

print(response.charset)
print(response.content)
return response

render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
Return a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)

参数:
1、request:用于生成响应的请求对象,固定必须传入的第一个参数

2、template_name:要使用的模板的完整名称,必须传入,render默认会去templates目录下查找模板文件

3、context:可选参数,可以传入一个字典用来替换模块文件中的变量,默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。

4、content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认为'text/html'

5、status:响应的状态码。默认为200

6、useing: 用于加载模板的模板引擎的名称。

综上,render的功能可以总结为:根据给定字典渲染模板文件,并返回一个渲染后的HttpResponse对象。简单示例如下

1
2
3
4
5
6
from django.urls import re_path
from app01 import views

urlpatterns = [
re_path(r'^$',views.index),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.shortcuts import render

def index(request):
return render(request, 'index.html', {'name': 'allen', 'tag': 'dsb'})

## 上述代码等同于
from django.shortcuts import render
from django.template import loader

def index(request):
t = loader.get_template('index.html')
dic = {'name': 'allen', 'tag': 'dsb'}

return HttpResponse(t.render(dic, request)
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{ name }}</p>
<p>{{ tag }}</p>
</body>
</html>

测试

1
##启动django,在浏览器中访问url地址http://ip地址:端口号/,会看到{{ name }} {{ tag }}被替换成了allen和dsb

redirect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
返回重定向对象,返回的状态码为302,第一个参数用来指定浏览器重定向的地址,可以是
##1、一个完全标准的URL地址,如'https://www.yahoo.com/search/'
##2、也可以是一个没有域名的绝对路径,如'/search/'
##3、或者是一个没有域名的相对路径,如'search/',与1、2直接跳转到指定的绝对路径不同,相对路径需要先与当前路径进行拼后才能跳转,例如:如果当前路径为http://127.0.0.1:8080/index/,拼接后的路径为http://127.0.0.1:8080/index/search/

def baidu(request):
return redirect("https://www.baidu.com")

## redirect重定向等同于下述操作
def baidu(request):
'''浏览器收到Location和302后就自动跳转'''
response=HttpResponse("",status=302) # 自定义响应头
# response['Location']='/register/'
response["Location"]="https:/www.baidu.com" # 发送Location响应信息指定跳转地址
return response

示例如下

1
2
3
4
5
6
7
from django.urls import re_path
from app01 import views

urlpatterns = [
re_path(r'^baidu/$', views.baidu),

]
1
2
3
4
5
6
7
8
from django.shortcuts import HttpResponse,redirect

def index(request):
return redirect('/login/') # 跳转到http://127.0.0.1:8000/login/
# return redirect('login/') # 跳转到http://127.0.0.1:8000/index/login/

def login(request):
return HttpResponse("login page")

测试:

1
启动django,在浏览器中访问url地址http://ip地址:端口号/index/,跳转到login页面

重定向转态码301与302的区别(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一、301302的异同。
1、相同之处:
301302状态码都表示重定向,具体点说就是浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址(浏览器会从响应头Location中获取新地址),用户看到的效果都是输入地址A后瞬间跳转到了另一个地址B

2、不同之处:
  301表示永久重定向,旧地址A的资源已经被永久地移除了,即这个资源不可访问了。
  302表示临时重定向,旧地址A的资源还在,即这个资源仍然可以访问。

A页面临时重定向到B页面,那搜索引擎收录的就是A页面。
A页面永久重定向到B页面,那搜索引擎收录的就是B页面。
从SEO层面考虑,302要好于301

二、重定向原因:
1、网站调整(如改变网页目录结构);
2、网页被移到一个新地址;
3、网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。

JsonResponse

JSON就是Javascript Object Notation.最早来源于JavaScript

json就是一个数据格式,一般保存在文件中,以.json作为文件扩展名

  1. json数据类型:
  • 字符串,必须以双引号圈起来

    • 数组,python里面的列表

    • json,python里面的字典格式

    • 数值,所有的整数,小数,布尔值都是数值

    • unidentified # 未定义

    • null python里的None

  1. 数组成员,可以是json中任何的数据类型,但是每一个成员之间必须以英文逗号隔开,最后一个成员后面不能带有逗号,否则报错!!!
1
2
[1,2,3]
[1,"xiaoming",true,false,[1,2,3],{"k":1,"w":0}]
  1. json成员的键必须是字符串格式,成员之间用逗号隔开,最后一个成员不能带有逗号、
1
2
3
4
5
6
7
8
{
"name":"xiaoming",
"age":30,
"son":{
"name":"xiaoxiaoming",
"age":3
}
}
  1. json文件中,一般只能存在一个json数据

json一般用于不同系统,不同项目之间传递数据用的, 向前端返回一个json格式字符串的两种方式

方式一:

1
2
3
4
5
6
7
8
9
10
11
12
13
def index(request):
import json
content={
"name":"xiaoming",
"age":30,
"son":{
"name":"xiaoxiaoming",
"age":3
}
}
content=json.dumps(content)
return HttpResponse(content=content,status=206,content_type="application/json")
# return HttpResponse(content=content)

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def index(request):
from django.http import JsonResponse
content={
"name":"xiaoming",
"age":30,
"son":{
"name":"xiaoxiaoming",
"age":3
}
}
return JsonResponse(content)
#### 如果是以列表的方式返回会报错
## TypeError at /app01/index/
## In order to allow non-dict objects to be serialized set the safe parameter to False.
def index(request):
from django.http import JsonResponse
content=[
{"id":10,"title":"qqqqq","pric":10},
{"id":11,"title":"wwwwww","pric":10},
]
return JsonResponse(content,safe=False)
#默认safe=True代表只能序列化字典对象,safe=False代表序列化字典以外的对象

二、异常处理

django内置异常处理视图

Web应用在线上运行的过程中出现错误,是不可避免的,常见错误如

1
2
3
4
1400400 Bad Request 是由于明显的客户端错误(例如,格式错误的请求语法,太大的大小,无效的请求消息或欺骗性路由请求),服务器不能或不会处理该请求。
2403:用户没有访问某一资源的权限
3404:请求的url地址不存在
4500:服务端出错

django框架为其中的一些常见错误提供了标准的错误页面,但它们仅限于DEBUG=True调试模式下使用,要想符合生产环境的需求,则需要我们进行自定制,配置过程如下

步骤1.在templates目录下的顶层创建错误页面400.html、403.html、404.html、500.html,注意文件内容自定义,但文件名与放置位置不可改变

templates/400.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>自定义400页面</h1>
</body>
</html>

templates/403.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>自定义403页面</h1>
<p>
{{ exception }}
</p>
</body>

templates/404.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>自定义404页面</h1>
<p>
{{ exception }}
</p>
</body>
</html>

templates/500.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>自定义500页面</h1>
</body>
</html>

步骤2.编写处理异常的视图

1
2
3
4
5
6
7
8
9
## 针对上述四种错误,django框架内置了处理异常的视图,如下
from django.views.defaults import bad_request # 用来处理400异常
from django.views.defaults import permission_denied # 用来处理403异常
from django.views.defaults import page_not_found # 用来处理404异常
from django.views.defaults import server_error # 用来处理500异常

## 此处由于篇幅问题,请读者自行查看源码,通过查看源码,可以知道
## 1、内置的异常处理视图会去固定位置找对应的文件名,这就是我们自定制错误页面文件位置与文件名固定的原因
## 2、内置的异常处理视图,针对400和500错误返回的就是静态页面,而针对403和404错误会用异常变量exception来渲染页面,我们在定制403.html、404.html页面时设定一个模板语法{{ exception }}就是用来专门接收异常值的

步骤3.请将 settings.py 中的 DEBUG设置为Fasle,这样,如果出现错误,就会出现我们自己定义的页面

1
2
3
4
5
## settings.py
DEBUG = False # 在生产环境中就应该设置成False

## ps:设置完DEBUG=False后,必须设置ALLOWED_HOSTS才能启动django,设为ALLOWED_HOSTS = ['*']即可
ALLOWED_HOSTS = ['*']

完成上述步骤后,我们来测试一下

1
2
3
4
5
6
7
8
9
10
from django.urls import path
from app01 import views

urlpatterns = [
path('test/400',views.error_test_400),
path('test/403',views.error_test_403),
path('test/404',views.error_test_404),
path('test/500',views.error_test_500),

]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.core.exceptions import SuspiciousFileOperation
from django.core.exceptions import PermissionDenied
from django.http import Http404


## 我们在编写正常视图view时会依据具体的逻辑,主动或被动抛出相对应的异常类型,此处为了测试精简,省略了正常逻辑,直接抛出异常
def error_test_400(request):
raise SuspiciousFileOperation('抛出400异常') # 异常信息不会展现在页面中


def error_test_403(request):
raise PermissionDenied('抛出403异常') # 异常信息会展现在页面中


def error_test_404(request):
raise PermissionDenied('抛出404异常') # 异常信息会展现在页面中

def error_test_500(request):
xxx # 会抛出异常NameError,异常信息不会展现在页面中

测试

1
2
3
4
5
## 在浏览器依次输入下述url地址,会执行对应的视图函数,触发异常,django内置的异常处理视图会捕捉异常并返回我们定制的错误页面
http://127.0.0.1:8002/test/400
http://127.0.0.1:8002/test/403
http://127.0.0.1:8002/test/404
http://127.0.0.1:8002/test/500

自定义异常处理视图

django内置的异常处理视图应该可以满足大部分Web应用的需求,但不可否认的是缺乏灵活性,比如我们想在400.html页面中也获取异常信息,此时就需要自定制400异常的处理视图了,为了满足读者日后的需求,我们在此将四个异常处理视图都自定义一下,在上例的基础上,作出如下操作

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>自定义400页面</h1>
<p>
{{ exception }}
</p>
</body>
</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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
## ================》下面是我们正常的应用视图《================
from django.core.exceptions import SuspiciousFileOperation
from django.core.exceptions import PermissionDenied
from django.http import Http404


## 我们在编写正常视图view时会依据具体的逻辑,主动或被动抛出相对应的异常类型,此处为了测试精简,省略了正常逻辑,直接抛出异常
def error_test_400(request):
raise SuspiciousFileOperation('抛出400异常') # 异常信息不会展现在页面中


def error_test_403(request):
raise PermissionDenied('抛出403异常') # 异常信息会展现在页面中


def error_test_404(request):
raise PermissionDenied('抛出404异常') # 异常信息会展现在页面中

def error_test_500(request):
xxx # 会抛出异常NameError,异常信息不会展现在页面中


## ================》新增自定义异常处理视图《================
from django.shortcuts import render

def my_custom_bad_request_view(request, exception): # 400
'''
处理400异常的视图,必须接收两个参数

参数request:
参数exception: 捕获的异常值

返回值: django规定该函数需要返回一个HttpResponseBadRequest类的对象
可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 400
from django.views.defaults import HttpResponseBadRequest

明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可
'''
print('====>400')
response = render(request, '400.html', {'exception': exception})
response.status_code = 400 # 如果不设置,默认状态码为200
return response


def my_custom_permission_denied_view(request, exception): # 403
'''
处理403异常的视图,必须接收两个参数

参数request:
参数exception: 捕获的异常值

返回值: django规定该函数需要返回一个HttpResponseForbidden类的对象
可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 403
from django.views.defaults import HttpResponseForbidden

明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可
'''
print('====>403')

response = render(request, '403.html', {'exception': exception})
response.status_code = 403 # 如果不设置,默认状态码为200
return response


def my_custom_page_not_found_view(request, exception): # 404
'''
处理404异常的视图,必须接收两个参数

参数request:
参数exception: 捕获的异常值

返回值: django规定该函数需要返回一个HttpResponseNotFound类的对象
可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 404
from django.views.defaults import HttpResponseNotFound

明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可
'''
print('====>404')

response = render(request, '404.html', {'exception': exception})
response.status_code = 404 # 如果不设置,默认状态码为200
return response


def my_custom_error_view(request): # 500
'''
处理500异常的视图,只接收一个参数

返回值: django规定该函数需要返回一个HttpResponseServerError类的对象
可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 500
from django.views.defaults import HttpResponseServerError

明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可
'''
print('====>500')

response = render(request, '500.html', ) # 服务端的错误本就不应该暴露给客户端
response.status_code = 500 # 如果不设置,默认状态码为200
return response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.urls import path
from app01 import views

urlpatterns = [
path('test/400',views.error_test_400),
path('test/403',views.error_test_403),
path('test/404',views.error_test_404),
path('test/500',views.error_test_500),

]

## 新增下列四行,下述变量名固定,对应的值为自定义异常处理视图
handler400 = 'app01.views.my_custom_bad_request_view' # 代表处理400异常指向自定义异常处理视图
handler403 = 'app01.views.my_custom_permission_denied_view'

handler404 = 'app01.views.my_custom_page_not_found_view'
handler500 = 'app01.views.my_custom_error_view'

三、FBV与CBV

引入

官网地址:https://docs.djangoproject.com/en/3.0/topics/class-based-views/

视图是可调用的,用来处理请求(request)并且返回响应(response),django的视图有两种形式:FBV和CBV

1
2
3
1、FBV基于函数的视图(Function base views),我们之前一直介绍的都是FBV

2、CBV基于类的视图(Class base views),我们本节主要介绍它

在早期,人们意识到视图开发的过程中存在一些常见的语法和模式,于是引入基于函数的通用视图来抽象这些模式,并简化了常见情况下的视图开发。因此,刚开始的时候只有FBV,django所做的事情就是向你定义的视图函数传递一个HttpRequest,并且希望返回一个HttpResponse。

随着进一步的发展,人们发现,基于函数的通用视图是有问题的,问题在于它很好地覆盖了简单的情况,但针对稍微复杂的场景,它没有办法在某些配置项之外进行扩展或自定义,从而极大地限制了它在许多实际应用程序中的实用性。而考虑到扩展性与自定义,这正是面向对象技术的强大之处,于是诞生了CBV。

基于类的通用视图与基于函数的通用视图目的都一样,都是为了让视图开发更加容易。但是CBV的解决方案是通过使用mixins,并且django内置了一系列通用视图作为工具集提供给我们继承使用,从而使得基于类的通用视图比基于函数的通用视图更具扩展性和灵活性。

如果你之前尝试过FBV,但发现它有缺陷,CBV是一种新的解决方案,而不是单纯地换了一种定义视图的形式,如果CBV只是视图的另外一种定义形式而已,那岂不是很无聊,连介绍的必要都没有了。

为了让读者快速掌握CBV的使用,我们只能从简单情况下举例,这看起来会是增加了代码的复杂度,读者可能会对其嗤之以鼻,其实不然,在读者日后遇到复杂的情况时,唯有CBV可以进行更高级的设计

综上,CBV并非是FBV的完全替代品,但相对于FBV,CBV确实有一些差异和优势,详解下一小节

1
2
3
1、针对不同的HTTP请求方法(如GET、POST),CBV可以分别对应到专门的处理方法上,无需采用条件分支,代码更加精简

2、CBV采用面向对象技术,比如mixins(多重继承)可用于将代码转换成可重用的组件。

使用CBV

使用不同的实例方法来响应不同的HTTP请求方法

CBV允许我们使用不同的实例方法来响应不同的HTTP请求方法,而不是像FBV那样使用条件分支代码

1.1 FBV中视图函数处理HTTP的GET请求如下

1
2
3
4
5
6
7
8
9
from django.http import HttpResponse

def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('GET result')
elif request.method == 'POST':
# <view logic>
return HttpResponse('POST result')

FBV关于url的配置略

1.2 CBV的实现如下

1
2
3
4
5
6
7
8
9
10
11
from django.http import HttpResponse
from django.views import View

class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('GET result')

def post(self, request):
# <view logic>
return HttpResponse('POST result')

所有基于类的视图都必须继承View类,该类用来处理视图关联到URL的操作,具体分析如下:

由于django的URL解析器期望发送request以及相关参数给一个可调用的函数,而不是一个类,所以基于类的视图有一个as_view()类方法(该方法继承自父类View),调用该方法会返回URL解析器所期望的函数,该函数会在请求到达与关联模式匹配的URL时调用,就像调用视图函数一个样子。查看源码会发现调用该函数首先会创建一个MyView类的实例,然后

1
2
3
4
1、调用self.setup()方法,用于初始化一系列属性。
2、之后调用self.dispatch(),该方法查看request以确定本次请求是GET还是POST等,如果定义了匹配方法,则将请求转发给匹配方法;如果没有,则引发HttpResponseNotAllowed。本例中将调用我们自定义的get与post方法来响应请求

ps:基于面向对象的知识,上述方法,如果子类MyView未重写,则去父类View中寻找

urls.py的配置如下

1
2
3
4
5
6
7
## urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
path('about/', MyView.as_view()),
]

虽然最精简的CBV不需要设置任何类属性就可以使用,但类属性确实在基于CBV的设计中非常有用,我们又两种方法可以配置或设置类属性

方法一:python标准的套路,在子类中重写/覆盖父类的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.views import View

class GreetingView(View):
greeting = "Good Day"

def get(self, request):
return HttpResponse(self.greeting)

## 可以在子类中对属性进行重写
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"

## 在url配置中使用子类调用as_view()方法即可
from django.urls import path
from myapp.views import MyView

urlpatterns = [
path('about/', MorningGreetingView.as_view()),
]

方法二:在URL配置里为as_view()方法传递关键字参数来设置类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]

## 注意1:as_view方法只接受已经是类属性的参数,即在类GreetingView中必须已经有greeting属性,as_view只是一种覆盖操作
from django.views import View

class GreetingView(View):
greeting = "Good Day" # 必须已经有了该属性

def get(self, request):
return HttpResponse(self.greeting)

## 注意2:这种覆盖操作只在本条url匹配成功时生效一次
urlpatterns = [
# 访问结果为覆盖后的属性值:"G'day"
path('about/', GreetingView.as_view(greeting="G'day")),
# 访问结果仍为原值:"Good Day"
path('about1/', views.GreetingView.as_view()),
]

使用mixins

Mixins机制指的是子类混合(mixin)不同类的属性和功能,比如在django内置的通用视图里有一个mixin类叫TemplateResponseMixin,它的主要目的就是来定义一个方法render_to_response()。当该类和View类组合到一起时,就得到了TemplateView类,TemplateView类分发请求到相应的方法上(通过执行继承自View类的dispath()方法),并且使用render_to_response()方法来找到template_name属性读取指定的模板从而返回一个TemplateResponse对象(通过执行继承自TemplateResponseMixin类的render_to_response()方法)

1
from django.views.generic.base import TemplateResponseMixin

这就极大利用了面向对象重之多继承技术来重用代码的优点,但是鱼与熊掌不可兼得,需要付出的代价就是,在多继承背景下,我们定义的mixin类越多,代码的可读性越差。

关于mixins的用法详见官网https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/,篇幅问题此处不再累述。

需要强调的是,CBV可以继承多个mixins类(推荐写在左边),但是只能继承一个父类,该父类只能是View类或者其子/子孙类(推荐写在右面),同时继承两个父类将会导致你的子类无法按照预期那样工作。

四、django内置的CBV

django内置了基本的CBV可以满足相当多的应用,所以的视图都继承自View类,View类负责处理将视图链接到url、HTTP方法调度和其他常见功能。内置的CBV如RedirectView提供了一个HTTP重定向,TemplateView扩展了基类,使其同时可以渲染一个模板。

1
from django.views.generic import View,TemplateView,RedirectView

最简单的使用

以TemplateView为例来介绍内置CBV的使用。最直接使用方式就是直接在URL配置中创建,如果只想改变该CBV的几个属性,你可以为as_view传递参数,如下

1
2
3
4
5
6
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
path('about/', TemplateView.as_view(template_name="about.html")),
]

一样的道理,如果我们想要重定向一个地址,可以设置RedirectView的url属性

1
2
3
4
5
6
7
8
from django.urls import path
from django.views.generic import RedirectView
from app01 import views

urlpatterns = [
path('index/', views.index),
path('about/', RedirectView.as_view(url="/index/")),
]

继承内置CBV进行重写

第二种更有效的使用内置CBV的方式就是是从现有视图继承并重写子类中的属性(例如template_name)或方法(例如get_context_data),以提供新的值或方法。

例如,我们的需求是要定制一个视图,该视图专门用来显示/渲染模板about.html。Django有一个内置的CBV即TemplateView可以实现,因此我们可以继承它,并重写模板名称template_name以及用来获取渲染模板数据的方法get_context_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## views.py
from django.views.generic.base import TemplateView


class HomePageView(TemplateView):
template_name = "home.html" # django会去templates目录下查找到该模板

def get_context_data(self, **kwargs): # 获取数据,用来渲染模块
context = super().get_context_data(**kwargs)

context['name'] = "egon"
context['age'] = "18"
context['title'] = "dsb"

return context
1
2
3
4
5
6
from django.urls import path
from app01 import views

urlpatterns = [
path('home/', views.HomePageView.as_view()),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<h1>{{ name }}</h1>
<h1>{{ age }}</h1>
<h1>{{ title }}</h1>

</body>
</html>

更多内置CBV详见:

https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-display/

五、使用CBV处理表单

请看完后续章节中关于表单的使用之后再来读本小节

FBV处理表单代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm # forms.py内的MyForm表单类

def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})

return render(request, 'form_template.html', {'form': form})

CBV处理表单代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm

class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'

def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})

def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')

return render(request, self.template_name, {'form': form})

上述只是一个很想的例子,但是我们可以定制这个视图,比如覆盖任意的类属性(例如form_class),或者为as_view()传参,或者继承MyForView并重写其中方法。

更多表单处理详见:

https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-editing/

六、为CBV添加装饰器

之前我们介绍过,若想要为CBV添加额外的功能,可以采用mixins机制,但除此之外,还可以通过添加装饰器的方法实现。有两个地方可以添加装饰

装饰as_view()方法

1
2
3
4
5
6
7
8
9
10
## 导入内置的装饰器
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]

示例:

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
## views.py
from django.shortcuts import render, HttpResponse
from django.views import View
import time

## 装饰器
def timer(func):
def wrapper(request, *args, **kwargs):
start = time.time()
ret = func(request, *args, **kwargs)
print("函数执行的时间是{}".format(time.time() - start))
return ret

return wrapper

## CBV视图
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')
1
2
3
from django.urls import path from app01.views import *

urlpatterns = [ path('login/',timer(LoginView.as_view())), ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>
用户名:
<input type="text" name="username">
</p>
<p>
密码:
<input type="password" name="password">
</p>
<p>
<input type="submit" value="登录">
</p>
</form>
</body>
</html>

装饰as_views()相当于给视图类的中的所有方法(例如get、post)都加上了装饰器

装饰视图类

类方法与独立函数不完全相同,所以你不能简单地把函数装饰器加到类方法上,这就用到了method_decorator方法,该方法会将函数装饰器转换为方法装饰器,然后才可以用来装饰类方法,例如:

装饰dispatch方法,基于CBV,当请求过来后会先执行dispatch()这个方法进而分发到对用的get、post方法上,所以如果需要批量装饰处理请求的方法(如get,post等)可以为dispatch方法添加装饰,这与为as_view()添加装饰器效果时一样的,如下

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
33
34
## views.py
from django.shortcuts import render, HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
import time

## 装饰器
def timer(func):
def wrapper(request, *args, **kwargs):
start = time.time()
ret = func(request, *args, **kwargs)
print("函数执行的时间是{}".format(time.time() - start))
return ret

return wrapper

## CBV视图
class LoginView(View):
#相当于给get,post请求都加上了装饰器
@method_decorator(timer)
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')

ps:url配置中正常调用即可,无需重复装饰,下同

1
2
3
4
5
from django.urls import path
from app01.views import *
urlpatterns = [
path('login/',LoginView.as_view()),
]

我们当然可以单独装饰不同的方法,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......省略代码,同上

class LoginView(View):
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

@method_decorator(timer) # 只装饰get方法
def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')

更简洁一点,我们可以把装饰器加在类上,通过name参数指定要装饰的方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## @method_decorator(timer,name='dispatch') # 批量添加
@method_decorator(timer,name='get') # 单独给get方法添加
## @method_decorator(timer,name='post') # 单独给post方法添加
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')

如果有多个装饰器需要添加,可以定义一个列表或者元组将其存放起来,然后按照下述方式指定

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
33
34
35
36
37
38
39
from django.shortcuts import render, HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
import time

## 定义多个装饰
def deco1(func):
def wrapper(request, *args, **kwargs):
print('===>deco1')
ret = func(request, *args, **kwargs)
return ret
return wrapper

def deco2(func):
def wrapper(request, *args, **kwargs):
print('===>deco2')
ret = func(request, *args, **kwargs)
return ret
return wrapper

## 定义装饰列表
decorators = [deco1, deco2]

@method_decorator(decorators,name='get') # 为get方法添加多个装饰器
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')

上述装饰器会按照列表规定的顺序依次执行装饰器,即先执行deco1,然后deco2。。。与下述添加方式是等同的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
......省略代码,同上

@method_decorator(deco1,name='get')
@method_decorator(deco2,name='get')
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
obj = super().dispatch(request, *args, **kwargs)
return obj

def get(self, request):
return render(request, "login.html")

def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'egon' and password == '123':
return HttpResponse('登录成功')
else:
return HttpResponse('账号或密码错误')

七、支持其他的HTTP方法

假设我们写了一个图书管理系统,如果有人想使用视图作为API通过HTTP协议访问我们的图书库。API客户端想要获取最新的图书数据,需要时不时地发起连接并下载自上次访问以来新出版的图书数据。但是如果自上次访问完后,就没有新的图书出版,那么从数据库查询图书信息,渲染一个完整的reponse并发送给客户端将白白耗费CPU时间和带宽,此时,我们最好的方式,就是在得知有新书出版时才发起一次请求完整数据的的API调用,这就用到了head方法来查询是否有数据更新

1
2
3
4
5
6
from django.urls import path
from app01.views import BookListView

urlpatterns = [
path('books/', BookListView.as_view()),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from django.shortcuts import HttpResponse
from django.views.generic import ListView
from app01.models import Book

class BookListView(ListView):
model = Book

def head(self, *args, **kwargs):
last_book = self.get_queryset().latest('publication_date')
response = HttpResponse()

response['Last-Modified'] = last_book.publication_date.strftime('%Y-%m-%d %X')
return response


def get(self,request):
'''
返回完整的数据,此处我们简单地用返回所有书籍名字代表将要返回的完整数据
'''
books=Book.objects.all()
names='|'.join([book.name for book in books])
return HttpResponse(names)
1
2
3
4
5
6
7
8
from django.db import models

## Create your models here.


class Book(models.Model):
name=models.CharField(max_length=30)
publication_date=models.DateTimeField(auto_now_add=True)
1
2
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False