Django 中有两种编写方式,FBV 和 CBV,那么什么是 FBV,CBV 又是什么呢?

一、什么是 CBV

FBV(function base views) 就是在视图里使用函数处理请求(常见)。
CBV(class base views) 就是在视图里使用类处理请求。

示例:

1、project/urls.py

from django.contrib import admin
from django.urls import path
from app.views import IndexView

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

2、app/views.py

from django.shortcuts import render, HttpResponse
from django.views import View


class IndexView(View):
    def get(self, request, *args, **kwargs):
        print('get')
        return HttpResponse('GET')

    def post(self, request, *args, **kwargs):
        print('post')
        return HttpResponse('POST')

可以看到所有的请求都是在类 IndexView 中处理的,它继承 View,不管是什么请求,都可以匹配到。

二、源码分析

1、CBV 在进行路由匹配时,执行 as_view() 方法,它是类 View 中的一个方法,源码 base.py

class View:
    @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

2、从上面的源码中我们可以看到 as_view(),返回 view() 方法。而 view() 又调用执行 self.dispatch(request, *args, **kwargs) 方法:

def dispatch(self, request, *args, **kwargs):
	"""
	首先要判断请求方法是不是在 self.http_method_names 中(即允许的方法列表中)
	通过反射,匹配相应方法 get、post、put、delete 等
	"""
    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)	# 如果匹配上了,就执行它,get(requesr, *args, **kwargs)

http_method_names

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

3、因此大致的执行流程为:

请求过来先执行 URL 中的 as_view() 方法
as_view() 返回 view() 方法
view() 方法又调用执行 dispatch() 方法
dispatch() 中通过反射的方式来匹配相应的请求,是 get 请求,就执行 get() 方法,如果是 post 请求的就执行 post() 方法。

三、重写 dispatch

从上面我们指定 CBV 中,请求过来,执行相应视图函数之前,都会先执行 dispatch() 方法。那么如果我们想在处理请求前执行某个方法或者就打印点别的东西,我们可以重写它。

方法一

from django.shortcuts import render, HttpResponse
from django.views import View


class IndexView(View):
    def dispatch(self, request, *args, **kwargs):
        print('before...')
        func = getattr(self, request.method.lower())
        ret = func(self, request, *args, **kwargs)
        print('after...')
        return ret

    def get(self, request, *args, **kwargs):
        print('get')
        return HttpResponse('GET')

    def post(self, request, *args, **kwargs):
        print('post')
        return HttpResponse('POST')

运行结果如下:

before...
get
after...

当有很多个类的时候,不可能每个类都写一个,可以写一个基类,其他类继承基类即可:

class BasePatch(object):
    def dispatch(self, request, *args, **kwargs):
        print('before...')
        func = getattr(self, request.method.lower())
        ret = func(self, request, *args, **kwargs)
        print('after...')
        return ret
    
class IndexView(BasePatch, View):
    pass

这样 IndexView 就会先去找基类 BaseView 中的 dispatch() 方法,而不是 View 中的。


方法二

也可以继承父类的 dispatch(),不用自己写反射逻辑:

class BasePatch(object):
    def dispatch(self, request, *args, **kwargs):
        print('before...')
        ret = super(BasePatch, self).dispatch(request,  *args, **kwargs)
        print('after...')
        return ret

四、CSRF

CBV 中如果想给某个函数免除 csrf_token 认证,可以通过装饰器的形式实现,但是需要注意的是,装饰器必须装饰在类上或者 dispatch 上

方法一

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt


class IndexView(View):
    @method_decorator(csrf_exempt)		# 这句
    def dispatch(self, request, *args, **kwargs):
        print('before...')
        func = getattr(self, request.method.lower())
        ret = func(self, request, *args, **kwargs)
        print('after...')
        return ret

方法二

装饰在类上,不用 dispatch

@method_decorator(csrf_exempt, name='dispatch')
class IndexView(View):
    def get(self, request, *args, **kwargs):
        pass