Django - 探究FBV 视图
设置响应方式
视图(Views) 是Django 的MTV 架构模式的V部分,主要负责处理用户请求和生成响应的响应内容,也可以理解为视图是MVC 架构里面的C部分(控制器), 主要处理功能和业务上的逻辑。我们习惯使用视图函数处理HTTP请求,即在视图里定义def 函数,这种方式称为FBV(Function Base views)。
返回响应内容
- HttpResonse('Hello Word') : 状态码200,请求响应成功
- HttpResonseRedirect('/'): 状态码302,永久重定向
- HttpReponsePermanentRedirect('/'): 状态码301, 永久重定向
- HttpResponseBadRequest('400'): 状态码400,请求错误
- HttpRespnseNotFound('404'): 状态码404,网页不存在或网页的URL失效
- HttpResponseForbidden('403'): 状态码403, 内有访问权
- HttpRespnseNotAllow('405'): 状态码405, 不允许使用该请求方式
- HttpRespnseServerError('500'): 服务器内部错误
- JsonResponse({'foo':'bar'}): 响应内容为json 数据
- StreamingHttpResponse(): 响应内容以流式输出
上述响应类主要来自于模块djanog.http ,该模块是实现响应功能的核心,以HttpResponse为例:
def demo(request):
resp_html = '<h1> this is resp html str<h1>'
return HttpResponse(resp_html, status=200)
响应:
从HttpResponse 的使用过程可知,如果要生成网页内容,就需要将html 以字符串的形式表示,如果网页内容过大,就会增加视图函数的代码量,同时也没有体现模板的作用,因此Django 在此基础上进行了封装处理,定义了reder、redirect 函数。
def render(
request, template_name, context=None, content_type=None, status=None, using=None
):
参数介绍:
- request: 浏览器向服务器发送的请求对象
- template_name: 设置模板文件名,用于生成网页内容
- context: 对模板上下文(模板变量)赋值,以字典格式表示,默认情况下是一个空字典
- content_type: 响应内容的数据格式,一般情况下默认值即可
- status: http状态码,默认200
- using:设置模板引擎,用于解析模板文件,生成网页内容
设置重定向
HttpResonseRedirect 或 HttpReponsePermanentRedirect 只支持路由地址而不支持路由命名的传入,为了进一步完善功能,Django在此基础上定义了重定向函数redirect,该函数支持路由地址或路由命名的传入,并且能通过函数参数来设置重定向的状态码。
源码如下:
def redirect(to, *args, permanent=False, **kwargs):
redirect_class = (
HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
)
return redirect_class(resolve_url(to, *args, **kwargs))
- 判断参数 permanent 的 真假性来选择重定向的函数。底层还是使用 HttpResponsePermanentRedirect 或 HttpResponseRedirect 进行 重定向
异常响应
# MyDjango\urls.py
# 全局404 页面配置,handler404 名称固定
handler404 = 'index.views.page_not_found'
# 全局500 页面配置,handler500 名称固定
handler500 = 'index.views.page_error'
# index\views.py
def page_not_found(request, exception):
return render(request, '404.html', status=404)
def page_error(request):
return render(request, '500.html', status=500)
在MyDjango 文件夹的urls.py 里设置 handler404 和 handler500,分别指向 index 文件夹的views.py 的视图函数 page_not_found 和 page_error。当用户请求不合理或者服务器发生异常时,Django 就会根据请求信息执行相应的异常。视图函数分别使用到模板 404.html 和 500.html,因此在template 文件夹新增:
# 404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>这是404页面</h2>
</body>
</html>
# 500.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>这是500页面</h2>
</body>
</html>
为了验证 404 和 500异常,需要对项目进行调整:
# MyDjangi\setting.py 以下参数进行调整:
DEBUG = False
ALLOWED_HOSTS = ['*']
访问一个不存在的路由:
文件下载
响应内容除了返回网页信息外,还可以实现文件下载功能,是网站常用的功能之一。Django 提供了三种方式实现文件下载功能,分别是 HttpResponse、StreamingHttpResponse、FileResponse,三者说明如下:
- HttpResponse 是所有响应过程的核心类,它的底层功能类是HttpResponseBase
- StreamingHttpResponse 是在HttpResponseBase 的基础上进行继承与重写的,它实现流式响应输出(流式响应输出是使用Python的迭代器将数据进行分段处理并传输的),适应于大规模数据响应和文件传输响应
- FileResponse 是 在 StreamingHttpResponse 的基础上进行继承与重写的,它实现文件的流式响应输出,只适用于文件传输响应
StreamingHttpResponse 源码如下:
class StreamingHttpResponse(HttpResponseBase):
streaming = True
def __init__(self, streaming_content=(), *args, **kwargs):
super().__init__(*args, **kwargs)
self.streaming_content = streaming_content
初始化参数说明如下:
- streaming_content的数据格式可以设为迭代器对象或字节流, 代表数据或文件
- 形参*args 和 **kwargs 设置HttpResponseBase 的参数,即响应内容的数据格式content_type 和 响应状态码等参数
FileResponse 源码如下:
class FileResponse(StreamingHttpResponse):
block_size = 4096
def __init__(self, *args, as_attachment=False, filename="", **kwargs):
self.as_attachment = as_attachment
self.filename = filename
self._no_explicit_content_type = (
"content_type" not in kwargs or kwargs["content_type"] is None
)
super().__init__(*args, **kwargs)
参数说明:
-
as_attachment的数据类型为布尔型,若为False,则不提供文件下载功能,文件将会在浏览器打开并读取,若浏览器无法打开文件,则将文件下载到本地计算机,但没有设置文件后缀名。若为True,则开启下载功能,将文件下载到本地计算机,并设置文件后缀名
-
filename 设置下载文件的文件名,可以与 as_attachment 联合使用。若as_attachment 为False,则参数filename 失效,若为True 并且filename为空,则使用该文件原有的文件名作为下载文件的文件名,反之以参数filename 作为下载文件的文件名
下面通过一个例子来实现文件下载的功能
# index\urls.py
urlpatterns = [
path('download1/', views.download1, name='download1'),
path('download2/', views.download2, name='download2'),
path('download3/', views.download3, name='download3')
]
# index\views.py
def download1(request):
file_path = 'F:\dog.jpg'
try:
resp = HttpResponse(open(file_path, 'rb'))
resp['content-type'] = 'application/octet-stream'
resp['Content-Disposition'] = 'attachment;filename=dog.jpg'
return resp
except Exception:
raise Http404('Download error')
def download2(request):
file_path = 'F:\dog.jpg'
try:
resp = StreamingHttpResponse(open(file_path, 'rb'))
resp['content-type'] = 'application/octet-stream'
resp['Content-Disposition'] = 'attachment;filename=dog.jpg'
return resp
except Exception:
raise Http404('Download error')
def download3(request):
file_path = 'F:\dog.jpg'
try:
resp = FileResponse(open(file_path, 'rb'),as_attachment=True, filename='dog_file_name.jpg')
return resp
except Exception:
raise Http404('Download error')
# templates\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="{% url 'index:download1'%}">HttpResponse-下载</a><br/>
<a href="{% url 'index:download2'%}">StreamingHttpResponse-下载</a><br/>
<a href="{% url 'index:download3'%}">FileResponse-下载</a><br/>
</body>
</html>
点击相应连接即可完成下载:
HTTP请求对象
获取请求信息
对于Django 来说, 当它接收到HTTP请求后,会根据HTTP请求携带的请求参数以及请求信息来创建一个 WSGIRequest 对象,并且作为视图函数的首个参数,这个参数通常写成reqeust,该参数包含用户所有的请求信息。源码如下:
class WSGIRequest(HttpRequest):
def __init__(self, environ):
它继承并重写类HttpRequest。若要获取请求信息,则只需要从类WSGIRequest 读取相关的类属性即可。下面对一些常用的属性进行说明。
- COOOKIES: 获取客户端的Cookie信息,以字典形式表示,并且键值对都是字符串类型
- FILES: 返回MultiValueDict对象,进而可以获得文件上传对象
- GET: 获取GET请求的请求参数。它是django.http.request.QueryDict 对象,操作起来;类似于字典
- POST: 获取POST请求的请求参数,它是 django.http.request.QueryDict 对象,操作起来类似于字典
- headers: 获取客户端的请求头信息,以字典形式存储
- method:获取当前请求的请求方式
- path:获取当前请求的路由地址
- session: 一个类似于字典的对象,用来操作服务器的会话信息,可临时存放用户信息
- user: 当Djano 启用 AuthenticationMiddleware 中间件时才可用。它的值是内置数据模型 User 的对象,表示当前登陆的用户。如果用户当前没有登录,那么user 将设为 django.contrib.auth.models.AnonymousUser 的一个实例。
由于类WSGIRequest 继承并重写类 HttpRequest ,因此类 HttpReqest 里定义的类方法同样适用于类WSGIRequest. 下面是一些常用方法
- is_secure(): 是否采用了HTTPS协议
- get_hosts: 获取服务器的域名。如果在访问的时候没有端口号,就会加上端口号。
- get_full_path:返回路由地址。如果该请求为GET请求并且设置有请求参数没返回路由地址就会将请求参数返回,如/?user=xy&pw=123
案例:
# index\views.py
def index(request:WSGIRequest):
if request.method == 'GET':
# 方法使用
print(f'request.is_secure(): {request.is_secure()}')
print(f'request.get_host(): {request.get_host()}')
print(f'request.get_full_path(): {request.get_full_path()}')
# 属性使用
print(f'request.COOKIES: {request.COOKIES}')
print(f'request.content_type: {request.content_type}')
print(f'request.content_params: {request.content_params}')
print(f'request.scheme: {request.scheme}')
# 获取GET 的请求参数
print(request.GET.get('user'), '')
return render(request, 'index.html')
elif request.method == 'POST':
print(request.POST.get('user'), '')
return render(request, 'index.html')
# templates\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>请求参数demo</h2>
<form action="" method="post">
<input type="text", name="user">
<input type="submit", value="提交">
</form>
</body>
</html>
请求:http://localhost:8001/?user=xy&pw=123
控制台输出:
request.is_secure(): False
request.get_host(): localhost:8001
request.get_full_path(): /?user=xy&pw=123
request.COOKIES: {'Pycharm-4604d619': '4744d5c6-1703-46ea-bfcf-5d83b0b0319e', 'Idea-86060a68': '3aa55617-e442-4911-9fb7-b157ae0a59c2'}
request.headers: {'Content-Length': '', 'Content-Type': 'text/plain', 'Host': 'localhost:8001', 'Connection': 'keep-alive', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Sec-
Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', 'Upgrade-Insecure-Requests': '1', 'User-Ag
ent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;
q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch
-Dest': 'document', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cookie': 'Pycharm-4604d619=4744d5c6-1703-46ea-bfcf-5d83b0b0319e; Idea-86060a
68=3aa55617-e442-4911-9fb7-b157ae0a59c2'}
request.content_type: text/plain
request.content_params: {}
request.scheme: http
request.GET.get('user'): xy
填写表单,控制台输出:
request.is_secure(): False
request.get_host(): localhost:8001
request.get_full_path(): /
request.COOKIES: {'Pycharm-4604d619': '4744d5c6-1703-46ea-bfcf-5d83b0b0319e', 'Idea-86060a68': '3aa55617-e442-4911-9fb7-b157ae0a59c2'}
request.headers: {'Content-Length': '7', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'localhost:8001', 'Connection': 'keep-alive', 'Pragma': 'no-cache', 'Cache-Con
trol': 'no-cache', 'Sec-Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', 'Upgrade-Insecure-
Requests': '1', 'Origin': 'http://localhost:8001', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Sec-Fetch-Site': 'same-origin'
, 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch-Dest': 'document', 'Referer': 'http://localhost:8001/', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Lan
guage': 'zh-CN,zh;q=0.9', 'Cookie': 'Pycharm-4604d619=4744d5c6-1703-46ea-bfcf-5d83b0b0319e; Idea-86060a68=3aa55617-e442-4911-9fb7-b157ae0a59c2'}
request.content_type: application/x-www-form-urlencoded
request.content_params: {}
request.scheme: http
request.POST.get('user'): py
文件上传
# index\urls.py
urlpatterns = [
path('', views.upload, name='upload')
]
# index\views.py
def upload(request: WSGIRequest):
"""文件上传功能"""
if request.method == 'POST':
files: MultiValueDict = request.FILES
print(f'file:{files}, type is {type(files)}')
my_file = files.get('my_file')
print(f'my_file:{my_file}, type is {type(my_file)}')
if my_file is None:
return HttpResponse('no files for upload')
from pathlib import Path
with open(Path('F:\download') / my_file.name, 'wb+') as f:
for chunk in my_file.chunks():
f.write(chunk)
return HttpResponse('upload over!')
else:
# 请求方法为get时,返回文件上传页面
return render(request, 'index.html')
# template\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>请求参数demo</h2>
<form action="" method="post", enctype="multipart/form-data">
<input type="file" , name="my_file">
<input type="submit" , value="文件上传">
</form>
</body>
</html>
访问localhost:8001,选择文件后点击上传文件:
控制台输出:
file:<MultiValueDict: {'my_file': [<InMemoryUploadedFile: 中国梦.txt (text/plain)>]}>, type is <class 'django.utils.datastructures.MultiValueDict'>
my_file:中国梦.txt, type is <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
以上输出说明:
- request.FILES: 返回的是MultiValueDict 对象
- files.get("my_file")L: 返回的是上传的文件对象InMemoryUploadedFile
class MultiValueDict(dict):
"""
A subclass of dictionary customized to handle multiple values for the
same key.
class InMemoryUploadedFile(UploadedFile):
"""
A file uploaded into memory (i.e. stream-to-memory).
"""
文件对象提供了以下属性来获取文件信息:
- my_file.name: 获取上床的文件名,包含文件后缀名
- my_file.size: 获取上传文件的文件大小
- my_file.content_type: 获取文件类型,通过后缀名判断文件类型
从文件对象获取文件内容,Django 提供了以下读取方式:
- my_file.read(): 从文件对象读取整个上传文件的数据,这个方法只适合小文件
- my_file.chunks(): 按流式响应方式读取文件,在for循环中进行迭代,将大文件分块写入服务器指定位置
- my_file.multiple_chuncks(): 判断文件对象的大小,当文件大于2.5MB时,该方法返回True,否则返回False。因此,可以根据该方法来选择用read方法还是采用chunks方法
如果上传的是视频文件,Django 用于封装文件的对象有变化:
file:<MultiValueDict: {'my_file': [<TemporaryUploadedFile: [阳光电影www.ygdy8.com].夏洛特烦恼.BD.720p.国语中字.rmvb (application/octet-stream)>]}>, type is <class 'django.utils.dat
astructures.MultiValueDict'>
my_file:[阳光电影www.ygdy8.com].夏洛特烦恼.BD.720p.国语中字.rmvb, type is <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/18170964