Django源碼閱讀 View.as_view()方法

Django源碼閱讀之View.as_view()方法

平時我們在使用django開發項目的時候,很可能一個url對應有兩種請求方式,get請求和post請求,這兩種請求方式我們要做的操作也不一樣。

如果我們不使用類視圖來實現這個需求,可能我們就需要寫兩個視圖函數,並且寫兩個url來對應,雖然說這樣是可以實現需求的但是感覺看起來不是那麼爽。

這個時候我們一般都是使用類視圖來完成這個需求,不知道類視圖怎麼用的,可以參考一下這篇博客django中類視圖詳解

這裏我就直接定義一個簡單的類視圖來舉個例子

from django.http import HttpResponse
from django.views.generic import View

class IndexView(View):
    def get(self,request):
        return HttpResponse('index view get method')

    def post(self, request):
        return HttpResponse('index view post method')

上面我們就定義好了一個簡單的類視圖,然後我們需要在url中進行配置,像下面這樣

from django.urls import path
from .views import IndexView

urlpatterns = [
    path('index/', IndexView.as_view())
]

這樣,我們就配置好了一個視圖與url的映射,當我們以get方法請求/index/這個url的時候,就會返回IndexView.get這個函數的執行結果,當我們以post方法請求/index/這個url的時候,就會返回IndexView.post這個函數的執行結果。這樣,我們就達到了同一個url的不同請求方法返回不同的數據的需求了。就比我們寫兩個url來達到這個效果看起來更爽一些。而且在類中我們可以做更多的函數中沒有的操作。

那麼爲什麼我們這樣寫了就能達到這樣的效果呢?平時我們在url中添加映射的時候,path函數中的第二個參數就爲我們對應的函數視圖,而當我們使用類視圖的時候,我們就寫成了IndexView.as_view(),也就第二個參數的值變成了這個函數的返回結果,那麼我們想要了解爲什麼這樣寫就能實現這樣的而效果,我們就要閱讀as_view()這個函數的源代碼了。

首先我們找到這個函數的代碼。

django.views.generic.View.as_view 函數

from django.utils.decorators import classonlymethod
from functools import update_wrapper

class View:
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    # 首先這個函數是被classonlymethod這個裝飾器裝飾的,其實這個位置叫它描述器更合適一下。我們先來看一下這個描述器幹了什麼樣的事情
    @classonlymethod
    '''
    # 這裏我們可以看到,這是一個類,而且繼承自classmethod,也就是我們以前常用的類方法的裝飾器
    # 然後重寫了__get__方法。
    
    # 可能大家看不懂爲什麼要這樣寫,但是沒關係,大家可以先去看一下描述器這個東西,在回來看這個位置就懂了
    # 這裏我就直接給大家說結果吧,當我們一個類寫了__get__方法的時候,我們使用這個類去裝飾一個類中的方法,就會先進入__get__方法。
    # 其實這個位置就和我們裝飾器差不多。這裏就不細說了,我麼直接看__get__方法吧
    class classonlymethod(classmethod):
    
        def __get__(self, instance, cls=None):
            # self: 就是當前類的實例對象
            # instance: 被裝飾的 類的方法 的類 的實例對象,這句話可能有點拗口。其實這個位置就是IndexView的實例對象, 
            # 當我們以實例對象調用被這個裝飾器的函數時,這個值就爲當前實例對象,如果我們以類方法的形式調用這個函數,那麼這個值就爲None
            # cls: 被裝飾的 類的方法 的類, 也就是IndexView這個類
            
            # 判斷instance是否爲None, 如果不是None,也就是以是實例對象來調用的這個方法
            if instance is not None:
                # 拋出異常,說 “這個方法只能是類(class)使用,實例對象(instance)不能使用。”
                raise AttributeError("This method is available only on the class, not on instances.")
            
            # 返回父類的__get__方法返回結果
            return super().__get__(instance, cls)
    '''
    
    # 所以上面這個裝飾器,其實和普通的classmethod這個裝飾器效果一樣,只是不允許使用實例調用這個類方法而已。而我們平時寫的類方法,是可以被實例調用的。
    def as_view(cls, **initkwargs):
        # **initkwargs:這裏面的值就是我們調用函數時傳入的所有的關鍵字參數,但是我們一般使用as_view時都沒有傳入參數,所以這個值一般都爲None
        # 並且initkwargs是一個字典對象
        
        # 遍歷這個字典,拿到的是key
        for key in initkwargs:
            # 如果這個key在類屬性http_method_names這個列表中,拋出異常。
            # 大概意思爲: 你嘗試通過get/put/post/...這些方法的名字作爲關鍵字參數傳入到<當前的這個類>中,不要這樣做
            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__))
            # 判斷當前這個類是否有這個屬性或方法,如果沒有,拋出異常
            # <當前的這個類>接收到了一個無效得的關鍵字key,as_view只能付接收已經存在的class的屬性
            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))

        # 定義一個view函數,request就是每個視圖中的request這個實例對象,*args表示所有的位置參數, **kwargs表示所有的關鍵字參數
        # 不知道這幾個參數爲什麼是這些值的,大家可以看一下python中的裝飾器相關概念,就能知道這幾個參數爲什麼是這些值了
        def view(request, *args, **kwargs):
            # cls就爲當前的這個類,這裏也就是IndexView, 
            # 所以這句代碼就是根絕**initkwargs參數,實例化這個類
            self = cls(**initkwargs)
            # 如果這個類有get方法但是沒有head方法
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                # 賦值self.head = self.get
                # 就是讓head方法等於get方法
                self.head = self.get
            # 繼續綁定傳入的參數值到實例對象的屬性中
            self.request = request
            self.args = args
            self.kwargs = kwargs
            # 返回self.dispatch函數的執行結果。並且傳入相關的參數,這個函數的代碼我們在下面閱讀。
            return self.dispatch(request, *args, **kwargs)
        
        # view就是上面我們定義的函數,因爲在python中,萬物皆對象,所以函數也是一個對象
        # 給函數綁定連個屬性,並賦值
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # update_wrapper這個裝飾器其實就是改變當前函數的一下文檔之類的,大家可以自己查看一下這個函數的用法, 這裏我也是直接說結果
        # update_wrapper有四個參數
        # 第一個參數爲需要裝飾的函數
        # 第二個參數爲爲提供信息的函數,也就是將這個函數的信息賦值給第一個參數的函數
        # 第三個參數(assigned)的默認值爲 : ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
        # 第四個參數(updated)的默認值爲 :('__dict__',)
        # update_wrapper函數的功能就是將第二個參數的這些屬一一的賦值給第一個參數指定的函數
        
        # 因爲這裏將update參數的的值賦值爲空元祖,所以這裏就是將cls的中(assigned指定的屬性,也就是上面的默認值)全部賦值給view
        update_wrapper(view, cls, updated=())
        # 這裏將assigned賦值爲空,也就是將cls.dispatch函數的__dict__屬性賦值給view的__dict__屬性
        update_wrapper(view, cls.dispatch, assigned=())
        # 然後返回view這個函數,也就是相當於path中的第二個參數的值就爲這個view
        return view

django.views.generic.View().dispatch 函數

def dispatch(self, request, *args, **kwargs):
    # 還是先來理解每個參數代表的含義
    # request: 就是每個視圖中的第一個參數,request實例
    # *args: 所有的位置參數
    # **kwargs:所有的關鍵字參數
    
    # request.method:就能得到當前請求的具體方法如POST,GET,...是一個字符竄類型的數據,大家可以隨便寫一個視圖函數打印一下這個值
    # request.method.lower()就能將當前請求的方法的名稱變爲小寫, 如post,get...
    # 如果當前請求方法變爲小寫,在self.http_method_names中 
    # self.http_method_name是一個類屬性,是一個列表,裏面存放了很多請求方法的字符竄,上面我們也說到了這個屬性
    if request.method.lower() in self.http_method_names:
        # getattr這個函數大家應該不陌生吧,就是從一個對象中得到一個屬性或方法
        # 這裏就是從self中得到request.method.lower()這個方法,如果沒有,得到的就爲self.http_method_not_allowed這個方法,下面會說一下這個方法的源碼的
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    # 如果請求的方法沒有在self.http_method_names這個列表中,賦值handler爲self.http_method_not_allowed
    else:
        handler = self.http_method_not_allowed
    
    # 執行handle函數,並返回結果。例如我們請求方法爲get,那麼這個handler就是IndexView().get方法,
    return handler(request, *args, **kwargs)

django.views.generic.View().http_method_not_allowed 函數

from django.http.response import HttpResponseNotAllowed
import logging

# 獲取名字爲'django.request'的logger, 這個實在django開始運行的視乎,設置的一個logger,想要了解具體是怎樣生成的這個logger
# 可以參考我前面的django源碼的閱讀 https://blog.csdn.net/xujin0/article/details/102533660
logger = logging.getLogger('django.request')

def http_method_not_allowed(self, request, *args, **kwargs):
    # request: 每個函數視圖中的第一個參數,request實例
    # *args: 所有的位置參數
    # **kwargs:所有的關鍵字參數
    
    # 在控制檯底部打印一些提示信息,也叫log日誌,這裏大家可以看一下logging模塊的使用,還是挺簡單的
    # extra中的值,會被綁定在logging.LogRecord這個對象上,我們在自定義logger中的額handle、formatter和filter中可以使用這個對象做一些操作
    logger.warning(
        'Method Not Allowed (%s): %s', request.method, request.path,
        extra={'status_code': 405, 'request': request}
    )
    # HttpResponseNotAllowed是一個繼承自HttpResponse的類,這個類就是修改了狀態碼爲405,就是返回一個HttpResponse對象
    '''
    # 這個函數就是找到這個類中寫了的所有相關請求方法,並返回。
    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]
    '''
    return HttpResponseNotAllowed(self._allowed_methods())

上面我們閱讀源碼的時候發現,如果請求的方法我麼沒有寫對應的函數,那麼就會執行django.views.generic.View().http_method_not_allowed這個函數。
所以我麼也可以重寫這個函數,返回一下我們自定義的頁面或者數據,來達到提示用戶的需求。

例如

from django.views.generic import View
from django.http import HttpResponse

class Index(View):
    def get(self, request):
        return HttpResponse('get method')

    def http_method_not_allowed(self, request, *args, **kwargs):
        resp = HttpResponse('你請求的姿勢不正確')
        # 設置狀態碼405,表示請求方法不允許
        resp.status_code = 405 
        return resp

上面我們就能夠自定義返回結果了。我們可以做很多我們想要的操作

到了這裏我們就讀完了as_view()函數的所有源代碼了,相信大家也知道了爲什麼path的第二個參數可以放as_view()函數的返回值了吧,其實這個函數就是相當於在根據請求方法,動態進入相應的視圖函數中。

同時我們也知道View中有一個類屬性http_method_names,裏面存放了所有支持的請求方法,那麼有時候我們也可以重寫這個屬性,來屏蔽一些方法,就算我們在視圖中寫了相應請求的方法,只要在http_method_names中沒有,也不會進入這個方法。這就是閱讀源碼的好處,我們能更好的理解框架是怎樣運行的,然後我們可以進行一些改裝,來實現一些我們的需求。

如果上面對源碼的理解有什麼錯誤的地方,歡迎大家想我提出來,我將不勝感激。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章