Static Badge Django 文档 Static Badge

主要业务逻辑重写点集中在这几个类,它们通过单继承来扩展:

以上,所有HTTP请求都会按照HTTP方法转发给同名类方法来处理。
通过混入 ViewSetMixin 允许一个视图类处理多个同种HTTP请求,比如列表的GET和详情的GET分别转发到自定义的 .list()retrieve() ,继而衍生出以下视图集类:

GenericAPIViewGenericViewSet 又可以通过混入以下几个基础的 Mix-in 衍生出一堆预制的视图类。

下面通过两条主线来浅析视图(集)。

dispatch线

Django 的 View 通过 .dispatch() 将接收到的 HttpRequest 转发到HTTP同名类方法处理并返回 HttpResponse ,流程如以下代码所示意:

from django.views.generic.base import View

class MeowView(View):

    # GET 请求将被转发到这个函数来处理
    def get(self, request, *args, **kwargs):
        pass

    # POST 请求将被转发到这里
    def post(self, request, *args, **kwargs):
        pass

    # OPTIONS、PUT、DELETE 等同理

方法的代码大致如下:

# 省略了很多细节...
def dispatch(self, request, *args, **kwargs):
    # 将请求转发到同名类方法来处理
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(),
                          self.http_method_not_allowed)
    # 如果找不到,或请求方法是非法的,
    # 就转到 self.http_method_not_allowed() 处理。
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

DRF 的 APIView 重载了 .dispatch(),主要作用与前者一样,但添加了

几个特性。其代码大致如下:

# 省略了很多细节...
def dispatch(self, request, *args, **kwargs):
    # 初始化request的一些东西,比如身份验证。
    request = self.initialize_request(request, *args, **kwargs)

    # 附加到对象中。在不能或不便传参的地方使用视图对象来获得当前请求。
    self.request = request

    try:
        # 初始化。身份验证、权限检查、频率限制就是在这里做的。
        # 可以继承重写添加自己的初始化,比如控制查询集范围等。
        self.initial(request, *args, **kwargs)

        # 将请求转发到同名类方法来处理
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        # 如果找不到,或请求方法是非法的,
        # 就转到 self.http_method_not_allowed() 处理。
        else:
            handler = self.http_method_not_allowed
        response = handler(request, *args, **kwargs)

    # 处理异常。注意这里拦截的是 Exception,像 KeyboardInterrupt 这些是拦截不到的。
    except Exception as exc:
        response = self.handle_exception(exc)

    # 整理response。在这里定制自己需要的返回值格式、响应码等等。
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

as_view线

.as_view() 是将视图类的初始化、执行等操作存储在一个函数中(有点装饰器那味儿),供路由调用,也就是处理请求和响应的主入口(Main entry point for a request-response process.)。

Viewas_view() 代码大致如下:

# 省略了很多细节...
@classonlymethod
def as_view(cls, **initkwargs):
    # 这个 initkwargs 是下面用来初始化 View 的参数,
    # 键不能是HTTP请求方法,也不能是 View 没有的属性;
    # 而回看 View 发现 __init__() 是自定义 self 的属性或方法,
    # 因此 as_view() 实际上是允许我们
    # 覆盖 View 对象原有的属性和方法(除了处理请求那部分)。

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        self.setup(request, *args, **kwargs)
        if not hasattr(self, 'request'):
            raise AttributeError()
        return self.dispatch(request, *args, **kwargs)

    view.view_class = cls
    view.view_initkwargs = initkwargs
    return view

APIViewas_view() 除了要求 视图类 不能直接存储查询集之外,也仅仅充当一个基类的作用,并没有添加什么东西。

代码大致如下:

# 省略了很多细节...
@classmethod
def as_view(cls, **initkwargs):

    view = super().as_view(**initkwargs)
    view.cls = cls
    view.initkwargs = initkwargs

    # 对基于会话的身份验证进行CSRF校验。
    return csrf_exempt(view)

ViewSetMixin 允许一个视图类处理多个同种HTTP请求,比如列表的GET和详情的GET分别转发到自定义的 .list()retrieve()

究其原因是在 MeowViewSet.as_view() 时将 MeowViewSet 对象的 list 复制到了 get 上,导致虽然 .dispatch() 虽然还是机械地转发GET请求到 .get() 、POST请求到 .post() ……,但此时调用的 .get() 实际上就是 .list() ,故而只要创建不同的 MeowViewSet 对象就能实现一句代码分别处理列表的GET和详情的GET。

代码大致如下:

# 省略了很多细节...
@classonlymethod
def as_view(cls, actions=None, **initkwargs):

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)

        if 'get' in actions and 'head' not in actions:
            actions['head'] = actions['get']

        # 从这里可以看出来,
        # actions 的键是 HTTP 方法名,值则是已经存在的方法的方法名。
        self.action_map = actions
        for method, action in actions.items():
            handler = getattr(self, action)
            setattr(self, method, handler)

        # 3.14.0 的版本依然有这段代码,
        # 感觉貌似是跟 dispatch() 里的重复了。
        self.request = request
        self.args = args
        self.kwargs = kwargs

        return self.dispatch(request, *args, **kwargs)

    view.cls = cls
    view.initkwargs = initkwargs
    view.actions = actions
    return csrf_exempt(view)