源碼解析Django CBV的本質

Django CBV模式的源碼解析

一般來講,http請求的本質就是基於Socketpython

Django的視圖函數,能夠基於FBV模式,也能夠基於CBV模式。django

基於FBV的模式就是在Django的路由映射表裏進行url和視圖函數的關聯,而基於CBV的模式則是在views.py文件中定義視圖類,在視圖類中視圖函數,如get,post,put,delete等瀏覽器

使用Django新建一個項目,新建一個路由映射markdown

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^cbv/$',views.CBV.as_view())
]

對應的views.py文件內容:app

from django.shortcuts import render,HttpResponse

from django.views import View

class CBV(View):
    def get(self,request):
        return HttpResponse("GET")

    def post(self,request):
        return HttpResponse("POST")

啓動項目,使用瀏覽器請求URLhttp://127.0.0.1:8000/cbv/,瀏覽器顯示結果爲:函數

請求到達Django會先執行Django中間件裏的方法,而後進行進行路由匹配。post

在路由匹配完成後,會執行CBV類中的as_view方法。url

CBV中並無定義as_view方法,因爲CBV繼承自Django的View,因此會執行Django的View類中的as_view方法spa

Django的View類的as_view方法的部分源碼code

class View(object):
    """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """

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

    def __init__(self, **kwargs):
        """ Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things. """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in six.iteritems(kwargs):
            setattr(self, key, value)

 @classonlymethod
    def as_view(cls, **initkwargs):
        """ Main entry point for a request-response process. """
        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)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        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=())
        return view

從View的源碼能夠看出,在View類中,先定義了http請求的八種方法

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

as_view方法中進行判斷,若是請求的方法沒在http_method_names中,則會拋出異常,這裏的cls實際上指的是自定義的CBV類

接着as_view方法中又定義view方法,在view方法中對CBV類進行實例化,獲得self對象,而後在self對象中封裝瀏覽器發送的request請求

self = cls(**initkwargs)

最後又調用了self對象中的dispatch方法並返回dispatch方法的值來對request進行處理

此時,因爲self對象就是CBV實例化獲得,因此會先執行自定義的CBV類中的dispatch方法。若是CBV類中沒有定義dispatch方法則執行Django的View中的dispatch方法

Django的View中的dispatch方法源碼

class View(object):
    """ 中間省略 """
    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        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)

在dispatch方法中,把request.method轉換爲小寫再判斷是否在定義的http_method_names中,若是request.method存在於http_method_names中,則使用getattr反射的方式來獲得handler

在這裏的dispatch方法中,self指的是自定義的CBV類實例化獲得的對象

從CBV類中獲取request.method對應的方法,再執行CBV中的方法並返回

由此,能夠知道若是在Django項目中使用CBV的模式,實際上調用了getattr的方式來執行獲取類中的請求方法對應的函數

結論:

CBV基於反射實現根據請求方式不一樣,執行不一樣的方法

自定義dispatch方法

若是想在基於CBV模式的項目中在請求某個url時執行一些操做,則能夠在url對應的類中定義dispatch方法

修改views.py文件

class CBV(View):
    def dispatch(self, request, *args, **kwargs):
        func = getattr(self,request.method.lower())
        return func(request,*args,**kwargs)

    def get(self,request):
        return HttpResponse("GET")

    def post(self,request):
        return HttpResponse("POST")

也能夠使用繼承的方式重寫dispatch方法:

class CBV(View):
    def dispatch(self, request, *args, **kwargs):
        print("before")
        res = super(CBV, self).dispatch(request, *args, **kwargs)
        print("after")
        return res

    def get(self,request):
        return HttpResponse("GET")

    def post(self,request):
        return HttpResponse("POST")

刷新瀏覽器,Django後臺打印結果以下:

瀏覽器頁面結果

同理,若是有基於CBV的多個類,而且有多個類共用的功能,爲了不重複,能夠單獨定義一個類,在這個類中重寫dispatch方法,而後讓url對應的視圖類繼承這個類

修改urls.py文件

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^cbv1/$',views.CBV1.as_view()),
    url(r'^cbv2/$',views.CBV2.as_view()),
]

views.py文件內容

from django.shortcuts import render,HttpResponse

from django.views import View

class BaseView(object):
    def dispatch(self, request, *args, **kwargs):
        func = getattr(self, request.method.lower())
        return func(request, *args, **kwargs)

class CBV1(BaseView,View):
    def get(self,request):
        return HttpResponse("CBV1 GET")

    def post(self,request):
        return HttpResponse("CBV1 POST")

class CBV2(BaseView,View):
    def get(self,request):
        return HttpResponse("CBV2 GET")

    def post(self,request):
        return HttpResponse("CBV2 POST")

經過python的面向對象能夠知道,請求到達視圖類時,會先執行CBV1和CBV2類中的dispatch方法,然而CBV1和CBV2類中並無dispatch方法,則會按照順序在父類中查找dispatch方法,此時就會執行BaseView類中的dispatch方法了

用瀏覽器請求urlhttp://127.0.0.1:8000/cbv1/,瀏覽器頁面顯示

用瀏覽器請求urlhttp://127.0.0.1:8000/cbv2/,瀏覽器頁面顯示

相關文章
相關標籤/搜索