这篇文章主要介绍两种方式实现批量操作
,
一种是使用 Django restframework提供的装饰器action,可以更具实际情况扩展默认的增删改查操作,扩展性很好;另外一种是使用第三方模块 djangorestframework-bulk
,这个模块简化了我们对于 对象本身增删改查的批量化操作
,各有优缺点。实际工作中选择合适的就好。
本次分享中使用到的model模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.db import modelsclass User (models.Model ): name = models.CharField(max_length=32 , unique=True , verbose_name='用户名' ) def __str__ (self ): return self.name class Student (models.Model ): name = models.CharField(max_length=32 ) def __str__ (self ): return self.name
注意这里两个模型存在的差异性,你发现差在哪里了吗?
1、使用action装饰器自定义批量方法 为了分享方便,这里把serializers、viewset等都放到了一个文件(selfaction.py)
中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from django.shortcuts import get_object_or_404from app.models import Student, Userfrom rest_framework.serializers import ModelSerializerfrom rest_framework.viewsets import ModelViewSetfrom rest_framework.decorators import actionfrom rest_framework.response import Responsefrom rest_framework import statusclass UserModelSerializer (ModelSerializer ): class Meta : model = User fields = '__all__' class UserModelViewSet (ModelViewSet ): serializer_class = UserModelSerializer queryset = User.objects.all () def get_serializer (self, *args, **kwargs ): serializer_class = self.get_serializer_class() kwargs.setdefault('context' , self.get_serializer_context()) if isinstance (self.request.data, list ): return serializer_class(many=True , *args, **kwargs) else : return serializer_class(*args, **kwargs) @action(methods=['delete' ], detail=False ) def multi_delete (self, request, *args, **kwargs ): ids = request.query_params.get('ids' , None ) if not ids: return Response(status=status.HTTP_404_NOT_FOUND) for id in ids.split(',' ): get_object_or_404(User, id =int (id )).delete() return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=['put' , 'PATCH' ], detail=False ) def multi_update (self, request, *args, **kwargs ): print ("args: " , args, " kwargs: " , kwargs) partial = kwargs.pop('partial' , None ) print ("partial: " , partial) instances = [] if isinstance (self.request.data, list ): for item in request.data: print (item) instance = get_object_or_404(User, id =int (item['id' ])) serializer = super ().get_serializer(instance, data=item, partial=partial) serializer.is_valid(raise_exception=True ) serializer.save() instances.append(serializer.data) else : pass return Response(instances)
分析说明: 1、通过action装饰器可以实现自定义接口,可以扩展当前默认的增删改查操作 2、action的methods是该方法可执行的http method, detail参数是确定方法是针对单条数据还是多条数据 3、实际验证过发现partial = kwargs.pop('partial', None)
不起作用,因为不管是PUT还是PATCH方法,该参数都是None,所以这里对于 partial的来源和用法有待进一步研究
1 2 args: () kwargs: {} partial: None
4、使用DRF的Response
返回的时候要求是JSON serializable
,网上有些教程直接把get_object_or_404
的结果instance追加到一个列表进行返回会报如下错误:
1 TypeError: Object of type User is not JSON serializable
所以追加到结果列表的时候使用 instances.append(serializer.data)
请求的接口验证 1、常规增删改查这里就赘述,可以参考文末整理的 《数据接口验证》 2、批量新增还是使用 /api/v1/users/
接口,只不过body体是 List 格式, 3、批量更新(put/patch) 接口/api/v1/users/multi_update/
4、批量删除() 接口/api/v1/users/multi_delete/?ids=1,2,3,4
这里的ids可以根据实际自定义
2、djangorestframework-bulk 以下配置可以放到一个文件或者放到几个独立的问题都可以
serializers.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from rest_framework.serializers import ModelSerializerfrom rest_framework.filters import SearchFilterfrom rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, BulkModelViewSetfrom app.models import Userfrom app.models import Studentclass UserSerializer (BulkSerializerMixin, ModelSerializer ): class Meta : model = User fields = '__all__' list_serializer_class = BulkListSerializer class StudentSerializer (BulkSerializerMixin, ModelSerializer ): class Meta : model = Student fields = '__all__' list_serializer_class = BulkListSerializer filter_backends = (SearchFilter, )
filters.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django_filters.rest_framework.filterset import FilterSetfrom django_filters import filtersfrom app.models import Userfrom app.models import Studentclass UserFilterSet (FilterSet ): class Meta : model = User fields = { 'id' : ['in' ], 'name' : ['icontains' ], } class StudentFilterSet (FilterSet ): class Meta : model = Student fields = { 'id' : ['in' ], 'name' : ['icontains' ], }
views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from django_filters.rest_framework import DjangoFilterBackendfrom rest_framework_bulk import BulkModelViewSetfrom app.models import User, Studentfrom app.serializers import UserSerializer, StudentSerializerfrom app.filters import UserFilterSet, StudentFilterSetclass UserViewSet (BulkModelViewSet ): serializer_class = UserSerializer queryset = User.objects.all () print (queryset) filter_backends = (DjangoFilterBackend, ) filter_class = UserFilterSet def allow_bulk_destroy (self, qs, filtered ): print ("here: qs-> " , qs, "\n filtered-> " , filtered) return qs is not filtered class StudentViewSet (BulkModelViewSet ): serializer_class = StudentSerializer queryset = Student.objects.all () filter_backends = (DjangoFilterBackend, ) filter_class = StudentFilterSet def allow_bulk_destroy (self, qs, filtered ): print ("here: qs-> " , qs, "\n filtered-> " , filtered) return qs is not filtered
urls.py
1 2 3 4 5 6 7 8 9 10 11 12 from rest_framework_bulk.routes import BulkRouterfrom app.views import UserViewSetfrom app.views import StudentViewSetfrom django.urls import path, includerouter = BulkRouter() router.register('users' , UserViewSet) router.register('students' , StudentViewSet) urlpatterns = [ path('' , include(router.urls)), ]
数据接口验证 下表整理了常规接口和批量操作的接口汇总,大家可以按照这个实际进行测试
知识点说明: 1、使用 restframework-bulk
批量删除时,是通过 allow_bulk_destroy
这样的hook
来判断要删除的结果是否是经过过滤的 qs is not filtered
;
1.1、所以必须定义过滤才可以,不然执行delete会不会删除
1 2 3 4 5 6 7 8 9 10 class StudentViewSet (BulkModelViewSet ): serializer_class = StudentSerializer queryset = Student.objects.all () def allow_bulk_destroy (self, qs, filtered ): print ("here: qs-> " , qs, "\n filtered-> " , filtered) return qs is not filtered
1.2、allow_bulk_destroy
返回True 代表删除,返回 False 代表不删除,如果一旦没有上面的过滤限制
,然后这个函数又直接返回True, 那就悲剧了…. 这个model的所有数据都被删除了…
1 2 3 4 5 6 7 8 9 10 11 12 class StudentViewSet (BulkModelViewSet ): serializer_class = StudentSerializer queryset = Student.objects.all () def allow_bulk_destroy (self, qs, filtered ): return True
2、还记得刚开始的User模型么,除了批量更新
之外所有的操作都是没有问题,为什么单独 批量更新
就问题呢? 报错如下,提示没有pk属性,但是实际上该表是有 id作为主键的
, id对应的就是 pk
明明和Student的代码一模一样。
一模一样吗? 当然存在差异,就是User模型中的name字段多了个 unique=True
去掉这个属性之后,再次执行批量更新操作没有任何问题。 但是最终未能找到问题的根源是什么? 大家有知道原因的,欢迎指导~
Refer: 1、https://zhuanlan.zhihu.com/p/369898862
感兴趣的可以扫码关注个人微信公众号,或者添加QQ 1209755822
备注技术交流