Django 檢視層

雲崖先生發表於2020-09-17

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

   JsonresponseDjango中自帶的一個基於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物件即是使用者傳送過來的資源請求,內建了很多方法及物件供我們使用。

   其實Djangorequest物件是基於wsgirefenv的一個封裝,是我們獲取資源請求中的資訊更加方便。

提交方式

   通過對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.GETrequest.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。關於enctypeAjax中已有介紹。

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方法。

  django-CBV原始碼分析

方法擴充套件

   基於上面的原始碼分析,反正都會執行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方法...")

相關文章