美文网首页
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 通用视图

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