美文网首页
rest_framework 通用视图

rest_framework 通用视图

作者: eeert2 | 来源:发表于2020-02-27 19:14 被阅读0次

通过继承rest_frameworkAPIView,我们可以完成视图类的编写,在大多视图类的编写中,我们的行为是如此的一致。

  • 获取 queryset,生成 Serializer对象,返回data数据
  • 根据pk获取模型object对象,进行更新操作 / 删除操作……
    ……
    rest_framework将视图开发中的某些通用习语和模式抽象化,就是通用视图

通用视图将一些标准行为固化成函数,例如大家统一使用get_querysetget_object来获取 instance实例对象,这样一来既提高了代码可读性,且便于后续扩展。


一、GenericAPIView 使用

GenericAPIView直接继承APIView, 是通用视图的基础, 它统一了视图行为与对应的函数名称,通用视图后续功能都是基于此类。

GenericAPIView类属性与默认值:

queryset = None
serializer_class = None
lookup_field = 'pk'
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

GenericAPIView常用方法:

get_queryset()   # 获取我们传入的 `queryset`
get_object()       # 根据 `lookup_field`与`lookup_url_kwarg`关键字 获取指定对象
get_serializer(*args, **kwargs) # 获取序列化对象
paginate_queryset(queryset) # 从结果集中获取分页后的数据

example 1. 简单使用queryset, serializer_class

# models.py
from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
# views.py
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework.response import Response


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        fields = '__all__'


class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()  # 设置`queryset`
    serializer_class = UserSerializer # 设置 `serializer_class`

    def get(self, request, *args, **kwargs):
        queryset = self.get_queryset() # 返回传入的`queryset`

        # 根据传入的`serializer_class`, 返回实例对象
        serializer = self.get_serializer(queryset, many=True) 
        return Response(serializer.data)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/users/', views.UserInfoView.as_view()),
]

http://127.0.0.1:8000/api/users/ 发起 get请求

[
    {
        "id": 1,
        "username": "韩美美",
        "password": "hmmhf45-s"
    },
    {
        "id": 2,
        "username": "李乐",
        "password": "94#jd"
    },
    {
        "id": 3,
        "username": "王昭君",
        "password": "sdf097^-sdf"
    },
    {
        "id": 4,
        "username": "李诗诗",
        "password": "sdj875*"
    },
    {
        "id": 5,
        "username": "孤独求败",
        "password": "sdfnk23"
    }
]

example 2. 使用get_object()获取对象

我们对单个对象进行操作的时候,通常是根据pk来获取对象,这也是get_object()的默认设置

# views.py

class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):
        obj = self.get_object()
        serializer = self.get_serializer(obj)
        return Response(serializer.data)
# urls.py
……
path('api/user/<int:pk>/', views.UserInfoView.as_view()),
……

访问http://127.0.0.1:8000/api/user/1/get 方法:

{
    "id": 1,
    "username": "韩美美",
    "password": "hmmhf45-s"
}

get_object()的源码讲解:

除去类型检查等,get_object()最主要的是执行以下一行代码:

queryset.get(*args, **kwargs)

从上面一行代码,我们可以看出:

    1. queryset 划出了我们的查询结果集,如果我们传入的queryset = UserInfo.objects.exclude(pk=1),那么访问http://127.0.0.1:8000/api/user/1/ ,就会报错"detail": "Not found."
    1. object根据kwargs 取值,在这里就是queryset.get(pk=1), kwargs实际是根据我们的类属性lookup_fieldlookup_url_kwarg, (默认 lookup_url_kwarglookup_field相同)生成的。
# `filter_kwargs` 将作为`queryset.get(*args, **kwargs)`的 参数
# `self.kwargs` 就是`url` 中传入的参数字典
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
  • 2.1 kwargskey就是我们传入的lookup_field值,如下:
class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    lookup_field = "username"

则执行get_object(),实际会执行 queryset.get(username=?)

  • 2.2 kwargs的值是从url路径中获取,如以下 url
path('api/user/<int:pk>/<str:name>/', views.UserInfoView.as_view()),

,我们想获取urlname作为kwargsvalue,则通过lookup_url_kwarg = "name"来指定。


example 3. 使用get_object() 配合 lookup_fieldlookup_url_kwarg获取对象

# views.py

class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username' # 指定`key` 
    lookup_url_kwarg = 'name' # 指定 `value`获取

    def get(self, request, *args, **kwargs):
        obj = self.get_object()
        serializer = self.get_serializer(obj)
        return Response(serializer.data)
# urls.py
……
path('api/user/<str:name>/', views.UserInfoView.as_view()),
……

访问
http://127.0.0.1:8000/api/user/韩美美/, 或者 http://127.0.0.1:8000/api/user/%E9%9F%A9%E7%BE%8E%E7%BE%8E/

{
    "id": 1,
    "username": "韩美美",
    "password": "hmmhf45-s"
}

example 4. 使用pagination_class 配合 paginate_queryset
关于分页器的使用,参考本文 https://www.jianshu.com/p/6b799871fc67

from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination

# 实现自己的分页器
class MyPagePagination(PageNumberPagination):
    page_size = 2 # 默认每页显示2条数据
    page_size_query_param = 'size' # 设置 控制每页条目数的参数
    max_page_size = 30 # 最大的条目数

# 实现序列化类
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        fields = '__all__'


class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()
    pagination_class = MyPagePagination
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):
        queryset = self.get_queryset() # 获取查询结果集
        queryset = self.paginate_queryset(queryset) # 将查询结果集进行分页
        serializer = self.get_serializer(queryset, many=True) # 将分页后的数据进行序列化
        return Response(serializer.data)

访问http://127.0.0.1:8000/api/users/?page=1&size=3get方法

[
    {
        "id": 1,
        "username": "韩美美",
        "password": "hmmhf45-s"
    },
    {
        "id": 2,
        "username": "李乐",
        "password": "94#jd"
    },
    {
        "id": 3,
        "username": "王昭君",
        "password": "sdf097^-sdf"
    }
]

二、GenericAPIView 的混入组件 mixin

我们通过 GenericAPIView 设置了通用行为 get_querysetget_objectget_serializerpaginate_querysetfilter_queryset

这些通用行为统一了大家的代码书写方式,例如在进行 get 查询结果集时:比较通用的代码如下:

def get(self, request, *args, **kwargs):
    queryset = self.get_queryset()
    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

针对上述行为,rest_framework提供了一套已经实现的组件mixin,供我们配合GenericAPIView使用:

  • CreateModelMixin
    实现了create 方法,创建对象

  • ListModelMixin
    实现了list方法,查询结果集 / 多个对象

  • RetrieveModelMixin
    实现了retrieve方法, 查询单个对象

  • UpdateModelMixin
    实现了update [更新对象全部信息] 与 partial_update [更新对象部分信息]

  • DestroyModelMixin
    实现了destroy,删除对象

点开 ListModelMixin 的源码:

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        # 根据我们设置的`filter_backends` 对`queryset`进行过滤
        queryset = self.filter_queryset(self.get_queryset())
        
        # 如果能够进行分页,则将分页后的数据进行返回
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        # 如果  `分页` 失败,则直接将数据返回
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

可以看出,rest_framework提供的小组件基本考虑到所有的可能,使用起来可以帮助我们节省更多的时间。

针对上述的 example 1example 4 情形,我们现在可以换一种更好的实现方式

from rest_framework import mixins

class UserInfoView(GenericAPIView, mixins.ListModelMixin):
    queryset = UserInfo.objects.all()
    pagination_class = MyPagePagination
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):
        
        # ListModelMixin 已经帮我们实现所以细节
        return self.list(request, *args, **kwargs)

针对上述的 example 2,可以配合 RetrieveModelMixin使用

from rest_framework import mixins

class UserInfoView(GenericAPIView, mixins.RetrieveModelMixin):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):

        # RetrieveModelMixin 已经帮我们实现所以细节
        return self.retrieve(request, *args, **kwargs)

三. ViewSetMixin 的使用

通过GenericAPIViewmixins 提供的CreateModelMixinListModelMixin等配合使用,我们可以不再自己去实现getpostputpatchdelete 方法,而是直接调用ListModelMixinlist方法,CreateModelMixincreate方法。

对于上述的请求分发控制工作,我们可以在getpost等方法中自己完成,但是对于这一工作,rest_framework提供了一种更便捷的方式,只需要我们的View类继承rest_framework.viewsets.ViewSetMixin

example 1. 使用ViewSetMixin

# views.py
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from my_app.models import UserInfo
from rest_framework import mixins
from rest_framework.viewsets import ViewSetMixin

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        fields = '__all__'

class UserInfoView(ViewSetMixin,
                   GenericAPIView,
                   mixins.RetrieveModelMixin,
                   mixins.CreateModelMixin,
                   mixins.ListModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   ):
    """
    UserInfoView 继承了 `retrieve`, `create`, `list`,
     `update`, `partial_update`, `destroy`
    """

    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
# urls.py

from django.urls import path
from my_app import views

urlpatterns = [
    # 将 `UserInfoView` 的`get`与`list`进行绑定
    path('api/users/', views.UserInfoView.as_view({'get': 'list', 'post': 'create',})),
    path('api/user/<int:pk>/', views.UserInfoView.as_view(
        {
            'get': 'retrieve',
            'put': 'update',
            'patch': 'partial_update',
            'delete': 'destroy'
        }
    )),
]

对于上述继承,我们需要先继承ViewSetMixin 后继承GenericAPIView等类。

这是因为ViewSetMixin重写了as_view()方法,所以我们需要把它放在前面,覆盖GenericAPIViewas_view()方法
ViewSetMixin源码:

def as_view(cls, actions=None, **initkwargs):
……
    # 如果我们传入的 actions = { 'get' : 'list' }
    for method, action in actions.items():

          # 获取 `View` 对象的 `list` 函数
          handler = getattr(self, action)
          
          # 将 `list` 函数 赋值给 `get`
          setattr(self, method, handler)
……

四.总结

    1. 视图部分,通过配合GenericAPIView ,mixin混入组件,ViewSetMixin控制分发,可以大大节省我们的时间,我们只需要传入GenericAPIView的基础设置,其中querysetserializer_class是必须项。
queryset = None  # 必须项
serializer_class = None # 必须项
lookup_field = 'pk' 
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
    1. 在继承上述类时,需要注意先后关系,必须ViewSetMixin在前,GenericAPIView在后。如果你记不住也没有关系,rest_framework已经安排了rest_framework.viewsets.GenericViewSet
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    这个类啥也不干,就是把继承关系弄好
    """
    pass
    1. rest_framework怕我们每次写继承关系太累,还安排了两个常用的View

rest_framework.viewsets.ReadOnlyModelViewSet

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):
    """
    A viewset that provides default `list()` and `retrieve()` actions.
    """
    pass

rest_framework.viewsets.ModelViewSet

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

相关文章

  • rest_framework 通用视图

    通过继承rest_framework的APIView,我们可以完成视图类的编写,在大多视图类的编写中,我们的行为是...

  • DRF_views_API向导

    一、请求(Request) REST_FRAMEWORK中的Request 视图类(APIView/@api_vi...

  • DRF-权限源码流程和基本使用

    django rest_framework 权限 在视图里面去设置权限 postman校验 基本使用 我们可以添加...

  • rest_framework之视图

    对请求响应到的视图函数的处理, 及视图函数的封装 注:mySer.BookSerializers为序列化的类 一,...

  • Django 框架之 通用视图

    知识点 通用视图概念和基本用法 基于类的视图的分类、写法和使用 简介 通用试图概念 通用视图是Django为解决建...

  • 通用视图

    这里又一次是这本书的反复出现的主题:最糟糕的是,Web开发是枯燥而单调的。 到目前为止,我们已经介绍了Django...

  • Django REST framework框架API接口序列化之

    一、重点是用到 rest_framework框架的 generics脚本文件,在view视图中导入 from re...

  • rest_framework 遇到`403`forbidden

    rest_framework默认是对所有的视图函数进行了csrf_exempt认证豁免。 如果你使用了postma...

  • 创建通用视图

    我们给应用创建了一个 default 视图,并将其放在 layouts 文件夹中,default 视图将作为整个应...

  • Django - 通用视图

    参考:http://www.yiibai.com/django/django_generic_views.html...

网友评论

      本文标题:rest_framework 通用视图

      本文链接:https://www.haomeiwen.com/subject/iotxhhtx.html