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\r\nContent-Disposition: form-data; name="file_1"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n\r\n------WebKitFormBoundary7YEgYjof25C6E8z9--\r\n'
請求頭
通過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方法...")