Django DRF 视图基类

1. 引子

Django REST framwork 提供了众多的通用视图基类与扩展类,以简化视图的编写。
REST framework 提供的视图的主要作用有:

  • 控制序列化器的执行(检验、保存、转换数据)
  • 控制数据库查询的执行

在此之前需要注意的是,DRF 是一个 app,也需要在配置文件中注册。

INSTALLED_APPS = [
    'django.contrib.admin',				# 后台管理,系统自带后台管理 admin
    'django.contrib.auth',				# 权限管理会生成6张表
    'django.contrib.contenttypes',		# 可以对所有 app 的表进行记录
    'django.contrib.sessions',			# session功能,也就是 django_session表
    'django.contrib.messages',			# django消息框架 flask的闪现
    'django.contrib.staticfiles',		# 静态资源相关
    'rest_framework',					# DRF 注册
]

2. 视图基类

2.1 APIView

APIView 是 REST framework 提供的所有视图的基类,继承自 Django 的 View 父类。
导入语句: rest_framework.views.APIView

2.1.1 APIView 与 View 的不同之处
  • 传入到视图方法中的是 REST frameworkRequest 对象,而不是 Django 的 HttpRequeset 对象;
  • 视图方法可以返回 REST framework 的 Response 对象,视图会为响应数据设置(render)符合前端要求的格式;
  • 任何 APIException 异常都会被捕获到,并且处理成合适的响应信息;
  • 在进行 dispatch() 分发前,会对请求进行身份认证、权限检查、流量控制。
  • 在 APIView 中仍以常规的类视图定义方法来实现 get() 、post() 或者其他请求方式的方法。
2.1.2 支持定义的类属性
  • authentication_classes 列表或元祖,身份认证类
  • permissoin_classes 列表或元祖,权限检查类
  • throttle_classes 列表或元祖,流量控制类
2.1.3 CBV 源码分析

通过对 cbv 源码和 drf 源码的分析可以了解其大致原理。

  • url.py 文件
调用视图函数类需要添加 'as_view()'函数,例如: path('home/', views.Test.as_view()),
  • as_view() 函数主要内容
@classonlymethod
def as_view(cls, **initkwargs):
    for key in initkwargs:
        if key in cls.http_method_names:
			...
        if not hasattr(cls, key):
			...

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        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__
            )
        return self.dispatch(request, *args, **kwargs)
    ...
    return view

解释:

函数 as_view 返回的值是 view,也就是实际上路由中执行的是 view 函数,而 as_view 中的 view 函
数返回的是 'self.dispatch(request, *args, **kwargs)'
  • dispatch 函数主要内容
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)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)
解释:

1. 首先判断了请求方式,http_method_names 中包含了诸多请求方式,如下所示:
	http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
2. 利用反射的方式获得类中和该请求方式同名的方法
3. 返回 'handler(request, *args, **kwargs)',将获得的方法调用并返还。
2.1.4 APIView 执行流程分析
  • url.py 文件
路由的使用并没有改变,需要添加 as_view() 。但是按照名称空间查找顺序,此时 as_view() 方法需要
去继承的 APIView 中查找相当于重写了父类的方法
  • APIView 中的 as_view() 方法
@classmethod
def as_view(cls, **initkwargs):
    if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
        def force_evaluation():
			...
        cls.queryset._fetch_all = force_evaluation

    view = super().as_view(**initkwargs)
    view.cls = cls
    view.initkwargs = initkwargs
    
    return csrf_exempt(view)
解释:

1. 重写了 View 中的方法,并继承了父类中的 as_view 方法。并且返回的值是 'csrf_exempt(view)'
2. csrf_exempt 方法可以忽略 csrf 校验,而 APIView 中没有 view ,也就是使用 APIView 父类 
   View 的 view 方法,只不过忽略了 csrf 校验。
  • view 方法
该方法和 cbv 中一致,返回的值是 dispatch 方法,但是该方法按照查找循序也是优先使用 APIView 中
的方法
  • APIView 中的 dispatch 方法
def dispatch(self, request, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    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)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response
    
解释:

1. 'request = self.initialize_request(request, *args, **kwargs)'包装新的 request对象
   以后视图类中用的 request 对象,就是新的 request
2. 'try' 异常捕获,将三大认证的异常以及执行视图函数的异常进行捕获。
3. 'self.initial(request, *args, **kwargs)' 内部封装了三大认证: 认证(登陆认证),频率(1分
   钟只能访问接口3),权限(普通用户不能访问,超级用户才能访问)
4. 在异常捕获代码内重写了原来 dispatch 的代码用于执行视图函数
5. 'self.response = self.finalize_response(request, response, *args, **kwargs)' 用于
   包装 response 响应对象,在浏览器中能看到美观的样式,在 postman 只看到 json 格式数据。

2.1.5 Request 对象

在上面的流程分析中发现 request 对象已经被包装过了,也就是说视图类中使用的 request 对象,是rest_framework.request.Request 的对象,原来的是django.core.handlers.wsgi.WSGIRequest

1. Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
2. 无论前端发送的哪种格式的数据,都可以统一的方式读取数据。

常见属性

  • data
    request.data 返回解析之后的请求体数据。类似于Django中标准的 request.POSTrequest.FILES 属性。request.POST 转换成普通字典可以使用 request.POST.dict()
  • query_params
    request.query_params 与Django标准的 request.GET 相同,可以获取请求地址中的数据
2.1.5 Request 对象源码分析
  • Request
class Request:
	def __init__(self, request, parsers=None, authenticators=None,
	            negotiator=None, parser_context=None):
    	...
    	self._request = request
    	...
解释:

在 __init__ 中把旧的 request 放到了新的 request 的 _request 属性中,接下来就是取出旧的
request 中的属性,逐个取比较麻烦,源码中重写了 '__getattr__' 方法
  • _getattr_ 方法
class Request:
	...
    def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)

解释:

该双下方法是在实例对象查找不存在的属性时执行,例如要取新的 request.POST,但是很明显没有,此时会
触发双下 __getattr__ 方法,使用反射在旧的 request 中获取属性。
2.1.6 自定义 data

request.data 只在 APIView 中有,若想在 View 中也能使用 data 可以使用装饰器或类的形式实现

  • 装饰器
from django.views.decorators.csrf import csrf_exempt, csrf_protect
import json

def outher(func):
    def inner(request, *args, **kwargs):
        
        try:
            data = json.loads(request.body)
        except Exception as e:
            data = request.POST
        request.data = data
        
        res = func(request, *args, **kwargs)
        return res

    return csrf_exempt(inner)

  • 视图函数
from django.shortcuts import render, HttpResponse
from django.views import View
from django.utils.decorators import method_decorator

class Test(View):
    def post(self, request):
        data = request.data
        print(data, type(data))  #  <class 'django.http.request.QueryDict'>
        return HttpResponse()

	# 重写dispatch对所有方法添加装饰器
    @method_decorator(outher)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

也可以通过类的形式。

import json
from django.views import View

class MyView(View):
    def dispatch(self, request, *args, **kwargs):

        try:
            data = json.loads(request.body)
        except Exception as e:
            data = request.POST
        request.data = data

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

  • 视图函数
from django.shortcuts import render, HttpResponse

class Test(MyView):
    def post(self, request):
        data = request.data
        print(data, type(data))
        return HttpResponse()

2.1.7 Response 对象

导入语句:from rest_framework.response import Response

REST framework 提供了一个响应类 Response,使用该类构造响应对象时,响应的具体数据内容会被转
换(render渲染)成符合前端需求的类型。
REST framework 提供了Renderer 渲染器,用来根据请求头中的 Accept(接收数据类型声明)来自动转
换响应数据到对应格式。如果前端请求中未进行 Accept 声明,则会采用默认方式处理响应数据,我们可以通
过配置来修改默认响应格式。

对请求和响应还可以进行配置,点击此文章: DRF 请求与响应

2.1.8 使用 APIView 编写 5 个接口

使用 APIView 编写查询单个、查询所有、删除单个、添加单个、编辑单个接口,代码示例如下(省略序列化类代码)。

视图函数

from rest_framework.response import Response
from rest_framework.views import APIView
from app01 import models
from app01.serializer import BookSerializer


class BookView(APIView):
    queryset = models.Book.objects
    serializer_class = BookSerializer

    def post(self, request):
        res = self.serializer_class(data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 100, "msg": "验证不通过", "info": res.errors})

    def get(self, request):
        res = self.serializer_class(self.queryset.all(), many=True)
        return Response(res.data)


class BookView1(APIView):
    queryset = models.Book.objects
    serializer_class = BookSerializer

    def get(self, request, **kwargs):
        res = self.serializer_class(self.queryset.filter(**kwargs).first())
        return Response(res.data)

    def delete(self, request, **kwargs):
        obj = self.queryset.filter(**kwargs).delete()
        return Response()

    def put(self, request, **kwargs):
        obj = self.queryset.filter(**kwargs).first()
        res = self.serializer_class(obj, data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 100, "msg": "验证不通过", "info": res.errors})

路由

from django.contrib import admin
from django.urls import path
from app01.view import views3

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views3.BookView.as_view()),
    path('books/<int:pk>', views3.BookView1.as_view()),
]

2.2 GenericAPIView

导入语句:rest_framework.generics.GenericAPIView

该类继承自 APIVIew主要增加了操作序列化器和数据库查询的方法,作用是为下面 Mixin 扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类

2.2.1 serializer_class

serializer_class 属性用于指明视图使用的序列化器,在视图类中可以指定使用的序列化器

使用方法:

  • get_serializer(self, *args, **kwargs)
  1. 返回序列化器对象,主要用来提供给 Mixin 扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
  2. 注意,该方法在提供序列化器对象的时候,会向序列化器对象的 context 属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
  3. request 当前视图的请求对象
    view 当前请求的类视图对象
    format 当前请求期望返回的数据格式
  4. 源码如下
def get_serializer(self, *args, **kwargs):
     """
     Return the serializer instance that should be used for validating and
     deserializing input, and for serializing output.
     """
     serializer_class = self.get_serializer_class()
     kwargs.setdefault('context', self.get_serializer_context())
     return serializer_class(*args, **kwargs)
  • get_serializer_class(self)
  1. 当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
  2. 源码如下,其返回的是类名,在视图类中使用需要加括号
def get_serializer_class(self):
    assert self.serializer_class is not None, (
        "'%s' should either include a `serializer_class` attribute, "
        "or override the `get_serializer_class()` method."
        % self.__class__.__name__
    )

    return self.serializer_class
2.2.2 queryset

queryset 指明使用的数据查询集,也可以在视图类中指定

使用的方法:

  • get_queryset(self)
  1. 返回视图使用的查询集,主要用来提供给 Mixin 扩展类使用,是列表视图与详情视图获取数据的基础,默认返回 queryset 属性
  2. 源码如下,简单来看就是若 queryset 为 none,通过assert 断言来抛出异常,然后将 queryset 属性取出判断是否添加 all() 方法后返回出去。
def get_queryset(self):
    assert self.queryset is not None, (
        "'%s' should either include a `queryset` attribute, "
        "or override the `get_queryset()` method."
        % self.__class__.__name__
    )

    queryset = self.queryset
    if isinstance(queryset, QuerySet):
        queryset = queryset.all()
    return queryset

  • get_object(self)
  1. 可以用于获取单个查询结果对象,主要用来提供给Mixin扩展类使用
  2. 若详情访问的模型类对象不存在,会返回404。
  3. 源码如下
def get_object(self):
    queryset = self.filter_queryset(self.get_queryset())
    # Perform the lookup filtering.
    lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

    assert lookup_url_kwarg in self.kwargs, (
        'Expected view %s to be called with a URL keyword argument '
        'named "%s". Fix your URL conf, or set the `.lookup_field` '
        'attribute on the view correctly.' %
        (self.__class__.__name__, lookup_url_kwarg)
    )

    filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
    obj = get_object_or_404(queryset, **filter_kwargs)

    # May raise a permission denied
    self.check_object_permissions(self.request, obj)

    return obj
2.2.3 使用 GenericAPIView 编写 5 个接口

与使用 APIView 编写的方式只是获取方法不同

视图函数

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from app01 import models
from app01.serializer import BookSerializer


class BookView(GenericAPIView):
	# 名字只能是这个
    queryset = models.Book.objects
    serializer_class = BookSerializer

    def post(self, request):
        res = self.get_serializer(data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 100, "msg": "验证不通过", "info": res.errors})

    def get(self, request):
        res = self.get_serializer_class()(self.get_queryset().all(), many=True)
        return Response(res.data)


class BookView1(GenericAPIView):
	# 名字只能是这个
    queryset = models.Book.objects
    serializer_class = BookSerializer

    def get(self, request, **kwargs):
        # res = self.get_serializer(self.get_object())  # 或者使用get_object()
        res = self.get_serializer(self.get_queryset().filter(**kwargs).first())
        return Response(res.data)

    def delete(self, request, **kwargs):
        obj = self.get_queryset().filter(**kwargs).delete()
        return Response()

    def put(self, request, **kwargs):
        obj = self.get_queryset().filter(**kwargs).first()
        res = self.get_serializer(obj, data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 100, "msg": "验证不通过", "info": res.errors})

路由

from django.contrib import admin
from django.urls import path
from app01.view import views2

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views2.BookView.as_view()),
    path('books/<int:pk>', views2.BookView1.as_view()),
]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值