Django视图层
Django
视图层相关的知识都是在APP
下的views.py
中所应用的,视图层的主要功能就是如何处理由路由层解析过来的request
请求,主要包含以下几块内容
1.如何处理
request
请求对象2.如何返回一个
HTML
文档或其他内容给前端页面3.如何对接上数据库实时获取数据
返回内容
HttpResponse
HttpResoponse()
接收一个字符串并返回。
from django.shortcuts import HttpResponse
def return_HttpResponse(request):
return HttpResponse("返回的内容")
redirect
redirect()
接受一个url
并返回,状态码为302
,即重定向。
from django.shortcuts import redirect
def return_redirect(request):
return redirect("http://www.google.com") # 跳转到www.Google.com
render
render()
有多个参数,其中第一个参数为返回request
请求对象,其他参数则用于返回HTML
文档及其局部命名空间内变量用作模板渲染。
参数 | 返回项 |
---|---|
参数1 | request对象 |
参数2 | HTML文档 |
参数3 | 局部命名空间变量(字典形式从换入),或locals()函数 |
from django.shortcuts import render
def return_render(request):
user_msg = {"name":"Yunya","age":18,"gender":"male"}
return render(request,"login.html",{"user_msg":user_msg})
# 参数3:{"user_msg":user_msg} 只传递user_msg到HTML文档
# 参数3:locals() 将当前函数的命名空间(return_render)下的所有变量传递到HTML文档
Jsonresponse
Jsonresponse
是Django
中自带的一个基于json
模块的封装,可以直接返回json
类型的数据至模板层的前端页面。
1.只支持字典,如果想传递其他数据类型需要将
safe
设置为False
2.如果想让转换后的
json
格式字符串中存在中文,请设置参数json_dumps_params={"ensure_ascii":False}
,即关闭自动转码
from django.http import JsonResponse
def f1(request):
data = {"name":"云崖","age":18}
return JsonResponse(data,json_dumps_params={"ensure_ascii":False}) # 只支持字典,关闭自动转码
扩展知识
其实不管是redirect
也好,reder
也罢,或者说Jsonresponse
也好,实际上内部都是返回了HttpResponse
,比如render
的模板替换其实原理非常简单,就是将后端的变量映射到模板上,就好比于字符串替换方法,把字符串替换成一个对象。
展示的模板页面其实就是经过替换之后来完成的。
def f1(request):
data = {"name":"云崖","age":18}
from django.template import Template,Context
res = Template("<h1>{{user}}<h1>") # 模板,将HTML代码渲染成模板
con = Context({"user":{"name":"Yunya"}}) # 替换的内容封装成Context对象
ret = res.render(con) # 执行替换
return HttpResponse(ret)
request
request
对象即是用户发送过来的资源请求,内置了很多方法及对象供我们使用。
其实Django
的request
对象是基于wsgiref
中env
的一个封装,是我们获取资源请求中的信息更加方便。
提交方式
通过对request.method
指向判断即可判定提交方式。
from django.shortcuts import render
from django.shortcuts import HttpResponse
def register(request):
if request.method == "GET":
print(request.GET)
return render(request,"register.html")
elif request.method == "POST":
print(request.POST)
username = request.POST.get("username")
password = request.POST.get("password")
hobby = request.POST.getlist("hobby")
print(
"""
username:{0},
password:{1},
hobby:{2}
"""
.format(username,password,hobby)
)
return HttpResponse("注册成功")
GET&POST
request.GET
与request.POST
都是两个QueryDict
对象,其中保存了用户提交过来的数据(不包含文件)。
如下示例,在地址栏中手动填入数据,request.GET
获取信息如下:
http://127.0.0.1:8000/register/?msg1=Django&msg2=request # 手动在地址栏填入的数据
print(request.GET)
<QueryDict: {'msg1': ['Django'], 'msg2': ['request']}>
数据获取
数据获取可使用get()
与getlist()
方法。
get()
用于从QueryDict
中获取单个值,但无法获取多个,如<QueryDict: {'hobby': ['music','game']}>
,那么获取到的始终是列表中的最后一个。而
getlist()
可获取多个,以列表形式进行返回,比如用户注册中有爱好等多选行为,应该使用getlist()
进行获取。
如下示例结合提交方式与数据获取,将一个视图函数分成两部分,结合不同的请求方式返回不同的请求内容。
from django.shortcuts import render
from django.shortcuts import HttpResponse
def register(request):
if request.method == "GET":
print(request.GET)
return render(request,"register.html")
elif request.method == "POST":
print(request.POST)
username = request.POST.get("username")
password = request.POST.get("password")
hobby = request.POST.getlist("hobby") # 获取用户爱好等多选项时,应该使用getlist
print(
"""
username:{0},
password:{1},
hobby:{2}
"""
.format(username,password,hobby)
)
return HttpResponse("注册成功")
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src='https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js'></script>
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css' integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u' crossorigin='anonymous'>
<script src='https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js' integrity='sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa' crossorigin='anonymous'></script>
<title>Document</title>
</head>
<body>
<h1 class="text-center">欢迎注册</h1>
<div class="container">
<div class="col-xs-8 col-sm-8 col-md-8 col-lg-8 col-xs-offset-2 col-sm-offset-2 col-md-offset-2 col-lg-offset-2">
<form action="" method="POST"> <!-- 当action不填时,提交至当前页面 -->
<p><input type="text" name="username" class="form-control"></p>
<p><input type="password" name="password" class="form-control"></p>
<p class="checkbox-inline"><input type="checkbox" name="hobby" value="basketball">篮球</p>
<p class="checkbox-inline"><input type="checkbox" name="hobby" value="football">足球</p>
<p></p>
<p><input type="submit" class="btn btn-success form-control"></p>
</form>
</div>
</div>
</body>
</html>
文件获取
对于form
中上传的文件,需要用request.FILES.get()
进行获取。进行写入的时候推荐使用chunkS()
(其实直接写也没啥问题)。
注意:前端必须为
POST
请求类型,并且必须指定enctype
属性为multpart/form-data
。关于enctype
在Ajax
中已有介绍。
def f1(request):
if request.method == "GET":
return render(request,"f1.html")
elif request.method == "POST":
file_obj = request.FILES.get("file_1")
print(file_obj.name)
print(file_obj.size)
import os
if not os.path.exists("./user_files"):
os.mkdir("./user_files")
with open("./user_files/{0}".format(file_obj.name),"wb") as f:
for line in file_obj.chunks(): # 推荐使用chunks()
f.write(line)
return HttpResponse("写入完成")
<form action="{% url 'app01:f1' %}" method="POST" enctype="multipart/form-data">
<p><input type="file" name="file_1"></p>
<p><button type="submit">上传文件</button></p>
</form>
路径获取
获取请求资源路径的方式有三种,两种属性一种方法,记住两种即可。
属性/方法 | 描述 |
---|---|
request.path | 只拿请求资源路径,不拿GET请求的数据 |
request.path_info | 同上 |
request.get_full_path() | 即拿请求资源路径,也拿GET请求中的数据 |
http://127.0.0.1:8000/app01/f1/?id=1&username=Yunya
print(request.path) # /app01/f1/
print(request.path_info) # /app01/f1/
print(request.get_full_path()) # /app01/f1/?id=1&username=Yunya
原生数据
通过request.body
属性可获取原生的资源路径请求,包括请求头,请求体等。
注意:拿到的是字节类型,这个是在页面请求方式中没有注明任何数据编码格式,将会放入
request.body
中
http://127.0.0.1:8000/app01/f1/ # POST方式提交,表单未input有个file类型,其他都没有了。
print(request.body)
b'------WebKitFormBoundary7YEgYjof25C6E8z9
Content-Disposition: form-data; name="file_1"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundary7YEgYjof25C6E8z9--
'
请求头
通过request.META
可获取到请求头,这个请求头非常有用。
它中间封装了很多属性,比如有一个属性就是来判断是将数据存放进request.body/request.POST/request.GET/request.FEILS
种的哪一个里面。
我们可以看一下request.POST
的源码:
def _get_post(self):
if not hasattr(self, '_post'):
self._load_post_and_files() # 执行
return self._post
# self._load_post_and_files() 方法中的两个重要判断
if self.content_type == 'multipart/form-data': # 判断请求头中请求方式的编码格式,这个会将POST提交中的文件放入reques.FILES里
elif self.content_type == 'application/x-www-form-urlencoded': # 判断是否有该请求头,有的话放入request.POST中
FBV&CBV
区别差异
FBV
即在视图层里通过函数的方式进行逻辑处理。
CBV
则是在视图层里通过类的方式进行逻辑处理。
CBV
对比FBV
有一个很明显的又是,就是能够自动的识别资源请求的提交方式。
其实CBV
内部就是FBV
。
基本使用
使用CBV
需要注意导入模块View
,并且书写的类要继承该类。
同时在urls.py
做路由解析时要使用as_view()
来进行解析。
from django.conf.urls import url
from app01 import views
urlpatterns = [
url('^c1/', views.C1.as_view()) # 注意,需要加括号
]
from django.shortcuts import HttpResponse
from django.views import View # 导入
class C1(View): # 必须继承
def get(self,request): # 当get请求来,执行该方法
return HttpResponse("get方法...")
def post(self,request): # 当post请求来,执行该方法
return HttpResponse("post方法...")
源码分析
CBV
如何做到区分请求方式的?这个要从路由层的as_view()
方法开始看。
@classonlymethod # 类方法
def as_view(cls, **initkwargs):
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs): # 闭包函数,现在不运行。下次的时候运行
self = cls(**initkwargs) # self == C1,即我们自己定义的类
if hasattr(self, 'get') and not hasattr(self, 'head'): # 如果有C1类有get方法并且没有head方法
self.head = self.get
self.request = request # 参数赋值
self.args = args # 参数赋值
self.kwargs = kwargs # 参数赋值
return self.dispatch(request, *args, **kwargs) # 执行并返回
view.view_class = cls # cls 即 C1 类
view.view_initkwargs = initkwargs # 赋值,不管
# take name and docstring from class
update_wrapper(view, cls, updated=()) # 不管
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=()) # 可以看到,这里的cls.dispatch是一个属性或是没运行的方法,根据属性查找顺序。往C1的继承类中找,会找到dispatch()方法,并且注意,即使是方法这里并没有执行,所以会接着往下走
return view # 返回view,接下来才执行。
经过闭包函数的返回,下面将会进行变形。
from django.conf.urls import url
from app01 import views
urlpatterns = [
# url('^c1/', views.C1.as_view()) # 注意,需要加括号
url('^c1/', views.C1.view) 自动执行view方法,其实就是执行闭包函数中的逻辑块
]
继续向下取找View
类中的dispatch
属性/方法。
def dispatch(self, request, *args, **kwargs):
# 说白了就是判断请求方式,是不是在这个 self.http_method_names 里头,如果在handler就是一个请求模式的方法,比如get或者post
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) # 执行并返回了
好了,也就是说最后的as_view()
会执行View.dispatch()
这个方法,这个方法内部其实就会执行GET
或者POST
方法。
方法扩展
基于上面的源码分析,反正都会运行dispatch()
,那么我们就可以对其进行方法扩展。
from django.shortcuts import HttpResponse
from django.views import View
class C1(View):
def dispatch(self, request, *args, **kwargs):
# 添加逻辑,如登录。必须登录后才能进行操作
return super(C1,self).dispatch(request, *args, **kwargs)
def get(self,request):
return HttpResponse("get方法...")
def post(self,request):
return HttpResponse("post方法...")
FBV装饰器
给FBV
添加装饰器,一般我们会单独将request
提取出来。
from django.shortcuts import HttpResponse
def wrapp(func):
def inner(request,*args,**kwargs):
return func(request,*args,**kwargs):
return inner
@wrapp
def other(request):
return HttpResponse("other...")
CBV装饰器
CBV
添加装饰器的方式有三种。针对不同的应用场景可以使用不同的装饰器添加方法,但是有一点是相同的。
都需要先导入下面这个模块:
from django.utils.decorators import method_decorator
方式一:为单独的某个功能添加装饰器。
from django.shortcuts import HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
def wrapp(func):
def inner(request,*args,**kwargs):
return func(request,*args,**kwargs):
return inner
class Other(View):
@method_decorator(wrapp)
def get(self, request):
return HttpResponse("get方法...")
@method_decorator(wrapp)
def post(self,request):
return HttpResponse("post方法...")
方式二:为CBV
类添加装饰器,可指定该装饰器作用与类下的那些方法(可添加多个)。
from django.shortcuts import HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
def wrapp(func):
def inner(request,*args,**kwargs):
return func(request,*args,**kwargs):
return inner
@method_decorator(wrapp,name="get")
@method_decorator(wrapp,name="post")
class Other(View):
def get(self,request):
return HttpResponse("get方法...")
def post(self,request):
return HttpResponse("post方法...")
方式三:为dispatch
方法添加装饰器,该装饰器会作用于所有的方法。(因为入口方法不是post
,就是get
)
from django.shortcuts import HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
def wrapp(func):
def inner(request,*args,**kwargs):
return func(request,*args,**kwargs):
return inner
class Other(View):
@method_decorator(wrapp)
def dispatch(self, request, *args, **kwargs):
return super(Other,self).dispatch(request, *args, **kwargs)
def get(self,request):
return HttpResponse("get方法...")
def post(self,request):
return HttpResponse("post方法...")