Django rest-framework视图家族

目录

视图家族

  • 视图类 GenericAPIView:包含两大视图类(APIView、GenericAPIView)
  • 视图工具类 mixins:包含五大工具类,六大工具方法
  • 工具视图类 generics:包含九大工具视图类,一堆mixins工具类与GenericAPIView视图基类组合
  • 视图集 viewsets:可自定义映射关系
from rest_framework import views,generics,mixins,viewsets

视图类 GenericAPIView

两大视图类:APIView、GenericAPIView

APIView

from rest_framework.views import APIView
  • 继承基本View,拥有View所有的功能
  • 重写了as_view()方法,禁用了csrf认证
  • 重写dispatch,封装请求、响应、渲染、异常、解析、三大认证模块
  • 封装一堆属性,可完成视图类的局部配置

GenericAPIView

from rest_framework.generics import GenericAPIView
  • 继承APIView,拥有APIView所有的功能
  • 提供get_queryset方法:配置queryset类属性,群查获取QuerySet对象
  • 提供get_object方法:配置lookup_url_kwarg类属性,单查获取单个对象
  • 提供get_serializer方法:配置serializer_class类属性,提供序列化类并使用自定义的序列化类序列化

总结:GenericAPIView就是在APIView基础上额外提供了三个方法和三个类属性,如果不配合视图工具类,则体现不出来优势所在

使用它的好处:视图中的增删改查逻辑其实大差不差,但操作的资源不一致(操作的资源指的是models模型类和序列化类),将资源形成配置,操作逻辑一致,就可以完成封装

使用GenericAPIView类

  • 继承GenericAPIView类
  • 配置对哪个表进行操作
  • 配置使用哪个序列化类

群查

from rest_framework.generics import GenericAPIView

class ViewGenericAPIView(GenericAPIView):
    # 配置关联表的属性
    # 如果只写models.Car.objects的话那就是manager对象,不是QuerySet对象
    queryset = models.Car.objects.filter(is_delete=False).all()
    # 配置使用的序列化类
    serializer_class = serializer.CarModelSerializer

    # 群查
    def get(self,request,*args,**kwargs):
        # 帮我们去表里面拿数据
        car_query = self.get_queryset()
        # 帮我们去序列化
        car_ser = self.get_serializer(car_query,many=True)
        return APIResponse(results=car_ser.data)

单查

from rest_framework.generics import GenericAPIView

class ViewGenericAPIView(GenericAPIView):
    # 配置关联表的属性
    # 如果只写models.Car.objects的话那就是manager对象,不是QuerySet对象
    queryset = models.Car.objects.filter(is_delete=False).all()
    # 配置使用的序列化类
    serializer_class = serializer.CarModelSerializer
    # 配置查询的条件为pk,单查走pk过滤的条件
    lookup_url_kwarg = 'pk'

    # 单查
    def get(self,request,*args,**kwargs):
        car_obj = self.get_object()
        car_ser = self.get_serializer(car_obj)
        return APIResponse(results=car_ser.data)

视图工具类 mixins

  • 在GenericAPIView的基础上提供了五个类,六个方法六大接口(单查、群查、单增、单整体改、单局部改、单删)
  • 使用的时候需要配合继承GenericAPIView类

五大工具类

  • RetrieveModelMixin:单查类
  • ListModelMixin:群查类
  • CreateModelMixin:单增类
  • UpdateModelMixin:单整体和单局部改类
  • DestroyModelMixin:单删类

六大工具方法

  • retrieve:单查方法
  • list:群查方法
  • create:单增方法
  • update:单整体改方法
  • partial_update:单局部改方法
  • destroy:单删方法

使用mixins的六大工具方法

  • 继承GenericAPIView类
  • 配置对哪个表进行操作
  • 配置使用哪个序列化类
from rest_framework import mixins

class ViewMixinsAPIView(mixins.RetrieveModelMixin,
                        mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        mixins.UpdateModelMixin,
                        mixins.DestroyModelMixin,
                        GenericAPIView):
    queryset = models.Car.objects.filter(is_delete=False).all()
    serializer_class = serializer.CarModelSerializer
    lookup_url_kwarg = 'pk'

    # 单查群查
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            return self.retrieve(request, *args, **kwargs)
        return self.list(request, *args, **kwargs)

    # 单增
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    # 单整体改
    def put(self,request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    # 单局部改
    def patch(self,request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    # 单删
    def delete(self,request, *args, **kwargs):
        # django中的删除是真正的删除
        # 删除接口一般是自己实现重写到的,因为真正的业务不需要真正的删除
        pass
        # django源代码中是真的删除
        return self.destroy(request, *args, **kwargs)

工具视图类 generics

工具类加视图类的组合,只要继承工具该类,就有响应的方法,

  • 帮我们将不同的mixins工具类与GenericAPIView视图类进行组合,我们不再需要继承GenericAPIView类
  • 不同的组合封装成一个个的类,实现对应的请求方法(get、post、put、patch、delete)

随后就是用单查就继承单查的接口,用群查就继承群查的接口即可。

使用generics的工具类实现接口

  • 配置对哪个表进行操作
  • 配置使用哪个序列化类
from rest_framework import generics

class ViewGenericsAPIView(generics.RetrieveAPIView,
                         generics.ListAPIView,
                         generics.CreateAPIView,
                         generics.UpdateAPIView,
                         generics.DestroyAPIView):
    # 单查和群查只能使用一个get,具体调用哪个要看继承的顺序
    queryset = models.Car.objects.filter(is_delete=False).all()
    serializer_class = serializer.CarModelSerializer
    lookup_url_kwarg = 'pk'

    # 有删除需求的接口继承DestroyAPIView,重写destroy完成字段的删除
    def destroy(self, request, *args, **kwargs):
        pass

视图集 viewsets

  • 重写as_view方法,增加action参数(可以完成路由层的请求方法映射关系)
  • 可以在路由层中自定义请求方法的映射关系

使用viewsets的视图集类实现接口

  • 配置对哪个表进行操作
  • 配置使用哪个序列化类

可自定义路由层中请求方法的映射关系来实现接口

路由层

url(r'^v5/cars/$', views.ViewViewsetsAPIView.as_view({
        "get":"list",
        "post":"create"
    })),
    url(r'^v5/cars/(?P<pk>\d+)/$', views.ViewViewsetsAPIView.as_view({
        "get":"retrieve",
        "put":"update",
        "patch":"partial_update",
        "delete":"destroy"
    })),

视图层

from rest_framework import viewsets
# 视图集类
class ViewViewsetsAPIView(viewsets.ModelViewSet):
    queryset = models.Car.objects.filter(is_delete=False).all()
    serializer_class = serializer.CarModelSerializer
    lookup_url_kwarg = 'pk'

完善viewsets的视图集类实现接口

以上的步骤我们继承视图集的ModelViewSet类实现了六大接口,但是从实际开发角度分析有很多不合理的点:

  1. 没有群增,群整体改,群局部改,群删四个接口
  2. 删除只做字段的修改
  3. 响应的结果只有数据,没有数据状态码和状态信息

所以针对以上问题,我们解决一下:

路由层配置

url(r'^v5/cars/$', views.ViewViewsetsAPIView.as_view({
        "get":"list",
        "post":"create",
        "put":"many_update",
        "patch":"many_partial_update",
        "delete":"many_destroy"
    })),
    url(r'^v5/cars/(?P<pk>\d+)/$', views.ViewViewsetsAPIView.as_view({
        "get":"retrieve",
        "put":"update",
        "patch":"partial_update",
        "delete":"destroy"
    })),

实现群增,群整体改,群局部改,群删四个接口

视图层配置

# 群整体改
    def many_update(self,request,*args,**kwargs):
        try:
            pks = []
            for dic in request.data:
                pks.append(dic.pop('pk'))
            car_query = models.Car.objects.filter(is_delete=False, pk__in=pks).all()
            if len(pks) != len(car_query):
                raise Exception('pk对象不存在')
        except Exception as e:
            return Response({'detail': '%s' % e}, status=400)
        car_ser = self.get_serializer(instance=car_query,data=request.data,many=True)
        car_ser.is_valid(raise_exception=True)
        car_obj = car_ser.save()
        return APIResponse(results=self.get_serializer(car_obj,many=True).data)

    
    
    # 群局部改
    def many_partial_update(self,request,*args,**kwargs):
        try:
            pks = []
            for dic in request.data:
                pks.append(dic.pop('pk'))
            car_query = models.Car.objects.filter(is_delete=False, pk__in=pks).all()
            if len(pks) != len(car_query):
                raise Exception('pk对象不存在')
        except Exception as e:
            return Response({'detail': '%s' % e}, status=400)
        car_ser = self.get_serializer(instance=car_query,data=request.data,many=True,partial=True)
        car_ser.is_valid(raise_exception=True)
        car_obj = car_ser.save()
        return APIResponse(results=self.get_serializer(car_obj,many=True).data)

    
    
    # 群删
    def many_destroy(self,request,*args,**kwargs):
        pks = request.data
        try:
            rows = models.Car.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
        except:
            return APIResponse(1, '数据有误')
        if rows:
            return APIResponse(msg='删除成功')
        return APIResponse(1, '删除失败')

    
    
    # 群增和单增必须使用同一个接口,都要走create方法,重写create方法,使用逻辑拆分
    def create(self, request, *args, **kwargs):
        if isinstance(request.data,list):
            car_ser = self.get_serializer(data=request.data,many=True)
            car_ser.is_valid(raise_exception=True)
            car_obj = car_ser.save()
            return APIResponse(msg="群增成功",results=self.get_serializer(car_obj,many=True).data)
        return super().create(request, *args, **kwargs)

实现删除只做字段的修改

# 解决2 destroy方法完成对字段的修改
    def destroy(self, request, *args, **kwargs):
        car_obj = self.get_object()
        if not car_obj:
            return APIResponse(1,msg="删除失败")
        car_obj.is_delete = True
        car_obj.save()
        return APIResponse(msg="删除成功")

实现返回信息包含数据状态码和状态信息

# 解决3 群查有状态码和状态信息,重写list方法
    def list(self, request, *args, **kwargs):
        response = super().list(request, *args, **kwargs)
        return APIResponse(results=response.data)

    # 重写retrieve方法,单查有状态码和状态信息
    def retrieve(self, request, *args, **kwargs):
        response = super().retrieve(request, *args, **kwargs)
        return APIResponse(results=response.data)

路由组件(了解)

使用SimpleRouter结合视图组件进行路由配置

from django.conf.urls import url,include
from rest_framework.routers import SimpleRouter
router = SimpleRouter()

# 将所有路由与ViewSet视图类的都可以注册,会产生'^v5/cars/$' 和 '^v5/cars/(?P<pk>[^/.]+)/$'的url
router.register('v5/cars',views.ViewViewsetsAPIView,basename='car')

urlpatterns = [
    # 第一种添加子列表方式
    url(r'^', include(router.urls)),
]
# 第二种添加子列表方式
# urlpatterns.extend(router.urls)

路由组件源码部分

如果想实现其他映射关系的话,修改源码就行了

routes = [
        # List route.
        # 群增,如果想要在url中奖请求方式映射关系改变的话,可以重写这个
        Route(
            url=r'^{prefix}{trailing_slash}$',
            mapping={
                'get': 'list',
                'post': 'create'
            },
            name='{basename}-list',
            detail=False,
            initkwargs={'suffix': 'List'}
        ),
        # Dynamically generated list routes. Generated using
        # @action(detail=False) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=False,
            initkwargs={}
        ),
        # Detail route.
        Route(
            url=r'^{prefix}/{lookup}{trailing_slash}$',
            mapping={
                'get': 'retrieve',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            },
            name='{basename}-detail',
            detail=True,
            initkwargs={'suffix': 'Instance'}
        ),
        # Dynamically generated detail routes. Generated using
        # @action(detail=True) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=True,
            initkwargs={}
        ),
    ]

或者自定义路由对象

from rest_framework.routers import Route, DynamicRoute, SimpleRouter as DRFSimpleRouter

class SimpleRouter(DRFSimpleRouter):
    routes = [
        # List route.  /资源s/
        Route(
            url=r'^{prefix}{trailing_slash}$',
            mapping={
                'get': 'list',  # 群查
                'post': 'create',  # 单增、群增
                'put': 'many_update',  # 群整改
                'patch': 'many_partial_update',  # 群局改
                'delete': 'many_destroy',  # 群删
            },
            name='{basename}-list',
            detail=False,
            initkwargs={'suffix': 'List'}
        ),
        # Dynamically generated list routes. Generated using
        # @action(detail=False) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=False,
            initkwargs={}
        ),
        # Detail route.  /资源s/(pk)/
        Route(
            url=r'^{prefix}/{lookup}{trailing_slash}$',
            mapping={
                'get': 'retrieve',  # 单查
                'put': 'update',  # 单整改
                'patch': 'partial_update',  # 单局改
                'delete': 'destroy'  # 单删
            },
            name='{basename}-detail',
            detail=True,
            initkwargs={'suffix': 'Instance'}
        ),
        # Dynamically generated detail routes. Generated using
        # @action(detail=True) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=True,
            initkwargs={}
        ),
    ]

# 对外提供router对象
router = SimpleRouter()
# eg: router.register('users', UserModelViewSet, basename='user')

相关推荐