Django视图类View源码分析

一、视图函数

django中的视图函数,就是视图功能由函数实现。

响应:或渲染模板后返回HTML,或直接返回JSON数据。

参数:视图函数的第一个参数必须是request对象。

文本响应
from django.http import HttpRequest,HttpResponse,JsonResponse
def test_index(request:HttpRequest):
    data = 'Test String'
    return HttpResponse(data)
JSON响应
from django.http import HttpRequest, HttpResponse, JsonResponse
def test_index(request:HttpRequest):
    data = [1, 2, 3]
    return JsonResponse(data)

这里会抛异常,TypeError: In order to allow non-dict objects to be serialized set the safe

parameter to False. 。意思是,safe参数为False才可使用非字典数据,所以,除非有必要,否则还

是使用字典

from django.http import HttpRequest, HttpResponse, JsonResponse
def test_index(request:HttpRequest):
    data = {'a':100, 'b':'abc'}
    return JsonResponse(data)
请求方法限制装饰器

需求:如果需要对请求方法进行限制,例如:只允许GET方法请求,可以自己判断,也可以使用Django提供的装饰器函数。

自己判断:

from django.http import HttpResponse, HttpRequest, JsonResponse
def index(request:HttpRequest, *args, **kwargs):
    print('~' * 30)
    print(request, args, kwargs)
    print(request.GET, request.method)
    print('~' * 30)
    if (request.method.lower() in ['get', 'post']):
        return JsonResponse({'a':100, 'b':'abc'})
    else:
        return JsonResponse({}, status=405)

装饰器函数

from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views.decorators.http import require_http_methods, require_GET, 
require_POST, require_safe
# @require_http_methods(['GET', 'POST'])
# @require_GET
@require_POST
def test_index(request:HttpRequest):
    data = {'a':100, 'b':'abc'}
    return JsonResponse(data)

url.py

urlpatterns = [
    path('',test_index), #路径映射,路径 为/emp
    #as_view 当做视图函数,伪装成视图函数,封装成视图函数
   # path('test/',TestView.as_view()),
    path('dujie/',test_index)
]

测试过程中,当使用不被允许的方法请求时,会返回405状态码,表示Method Not Allowed ,如下图

装饰完后,test_index 就是新的视图函数,装饰器内部的inner函数。

image

image

二、视图类

视图类就是视图功能由一个类和其方法实现

参考:https://docs.djangoproject.com/en/3.2/topics/class-based-views/

image

View类实现原理分析

可以看到官方文档上实现视图类,在urlpatterns定义中,路径映射的应该是函数,而AboutView是一个类并不是函数,所以他使用的是AboutView.as_view() ,下面来分析下,

一级路由配置如下:

# 主路由,一级路由,根路由
urlpatterns = [
    path('emp/', include('employee.urls')) # /emp/
]

employee/urls.py如下

# 二级路由,应用路由
from django.urls import path
from .views import TestView # 视图类
urlpatterns = [
    path('test/', TestView.as_view()), # /emp/test/
]

django.views.View类,定义了http的方法的小写名称列表,这些小写名称其实就是处理请求方法名的小写。

View类的类方法as_view()方法调用后返回一个内建的view(request,args,*kwargs)新函数,本质上,其实url还是映射到这个函数上。

请求request到来后,直接发给TestView.as_view() 函数,TestView.as_view()函数内部

  • 构建TestView实例self。下面可以看源码,每一个请求都会创建一个实例
  • dispath派发请求,self.dispath(request,args,*kwargs)

dispath方法内部对比请求方法method,如果存在请求的get、post等方法,则调用,否则返回405,

本质上,as_view()方法还是把一个类封装成一个视图函数。
这个视图函数,内部使用了一个分发函数,使用请求方法名称把请求分发给存在的同名函数处理

as_view源码分析

 ##请求方法列表
 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

  ## classonlymethod 规定了as_view只能由class调用,所以只能是TestView.as_view()这么调用
@classonlymethod
## 这里的第一个参数cls就是TestView
def as_view(cls, **initkwargs):
    ## 这个函数其实就是url真正映射的视图函数,第一个参数必须是request
    def view(request, *args, **kwargs):
        ## 这里就是将传入到class类实例化,每一个请求都会实例化一次,互补干扰
        self = cls(**initkwargs)
        self.setup(request, *args, **kwargs)
        if not hasattr(self, 'request'):
            raise AttributeError(
                "%s instance has no 'request' attribute. Did you override "
                "setup() and forget to call super()?" % cls.__name__
            )
        ##dispath 派发请求
        return self.dispatch(request, *args, **kwargs)
    ## 将视图函数返回
    return view

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.
    ## 这里判断请求的类型是否在定义好的method_names列表中
    if request.method.lower() in self.http_method_names:
    #如果存在则调用反射函数,判断是否有相关get、post等方法,如果没有则调用默认值函数self.http_method_not_allowed返回405
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
    #如果请求方法不存在定义好的列表中,则直接调用notallow函数返回405
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

def http_method_not_allowed(self, request, *args, **kwargs):
    logger.warning(
        'Method Not Allowed (%s): %s', request.method, request.path,
        extra={'status_code': 405, 'request': request}
    )
    return HttpResponseNotAllowed(self._allowed_methods())
视图类实现
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views import View
class TestIndex(View):
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)
    def get(self, request): # 支持GET
        data = {'a':100, 'b':'abc'}
        return JsonResponse(data)
    def post(self, request): # 支持POST
        data = {'a':200, 'b':'xyz'}
        return JsonResponse(data)