rest_framework 通用视图

2020-02-27  本文已影响0人  eeert2

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

通用视图将一些标准行为固化成函数,例如大家统一使用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)

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

# `filter_kwargs` 将作为`queryset.get(*args, **kwargs)`的 参数
# `self.kwargs` 就是`url` 中传入的参数字典
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
class UserInfoView(GenericAPIView):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    lookup_field = "username"

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

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使用:

点开 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)
……

四.总结

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
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    这个类啥也不干,就是把继承关系弄好
    """
    pass

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
上一篇 下一篇

猜你喜欢

热点阅读