深度解析Django REST Framework 批量操作
2021/5/5 18:56:54
本文主要是介绍深度解析Django REST Framework 批量操作,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
我们都知道Django rest framework这个库,默认只支持批量查看,不支持批量更新(局部或整体)和批量删除。
下面我们来讨论这个问题,看看如何实现批量更新和删除操作。
DRF基本情况
我们以下面的代码作为例子:
models:
from django.db import models # Create your models here. class Classroom(models.Model): location = models.CharField(max_length=128) def __str__(self): return self.location class Student(models.Model): name = models.CharField(max_length=32) classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE) def __str__(self): return self.name
serializers:
from .models import Classroom, Student from rest_framework.serializers import ModelSerializer class StudentSerializer(ModelSerializer): class Meta: model = Student fields = "__all__" class ClassroomSerializer(ModelSerializer): class Meta: model = Classroom fields = "__all__"
views:
from rest_framework.viewsets import ModelViewSet from .serializers import StudentSerializer, ClassroomSerializer from .models import Student, Classroom class StudentViewSet(ModelViewSet): serializer_class = StudentSerializer queryset = Student.objects.all() class ClassroomViewSet(ModelViewSet): serializer_class = ClassroomSerializer queryset = Classroom.objects.all()
myapp/urls:
from rest_framework.routers import DefaultRouter from .views import StudentViewSet, ClassroomViewSet router = DefaultRouter() router.register(r'students', StudentViewSet) router.register(r'classrooms', ClassroomViewSet) urlpatterns = router.urls
根urls:
from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('', include('myapp.urls')), ]
这是一个相当简单而又经典的场景。其中的Classroom模型不是重点,只是为了丰富元素,展示一般场景。
创建数据:
- 通过post方法访问127.0.0.1:8000/classrooms/创建一些教室数据。
- 通过post方法访问127.0.0.1:8000/students/创建一些学生数据。
可以很清楚地看到DRF默认:
- 通过GET
/students/
查看所有的学生 - 通过GET
/students/1/
查看id为1的学生 - 通过POST
/students/
携带一个数据字典,创建单个学生 - 通过PUT
/students/1/
整体更新id为1的学生信息 - 通过PATCH
/students/1/
局部更新id为1的学生信息 - 通过DELETE
/students/1/
删除id为1的学生
没有批量更新和删除的接口。
并且当我们尝试向/students/
,POST一个携带了多个数据字典的列表对象时,比如下面的数据:
[ { "name": "alex", "classroom": 1 }, { "name": "mary", "classroom": 2 }, { "name": "kk", "classroom": 3 } ]
反馈给我们的如下图所示:
错误提示:非法的数据,期望一个字典,但你提供了一个列表。
至于尝试向更新和删除接口提供多个对象的id,同样无法操作。
可见在DRF中,默认情况下,只能批量查看,不能批量创建、修改和删除。
自定义批量操作
现实中,难免有批量的创建、修改和删除需求。那怎么办呢?只能自己写代码实现了。
下面是初学者随便写的代码,未考虑数据合法性、安全性、可扩展性等等,仅仅是最基础的实现了功能而已:
批量创建
class StudentViewSet(ModelViewSet): serializer_class = StudentSerializer queryset = Student.objects.all() # 通过many=True直接改造原有的API,使其可以批量创建 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)
DRF本身提供了一个ListSerializer,这个类是实现批量创建的核心关键。
当我们在实例化一个序列化器的时候,有一个关键字参数many,如果将它设置为True,就表示我们要进行批量操作,DRF在后台会自动使用ListSerializer来替代默认的Serializer。
所以,实现批量创建的核心就是如何将many参数添加进去。
这里,我们重写了get_serializer方法,通过if isinstance(self.request.data, list):
语句,分析前端发送过来的数据到底是个字典还是个列表。如果是个字典,表示这是创建单个对象,如果是个列表,表示是创建批量对象。
让我们测试一下。首先,依然可以正常地创建单个对象。
然后如下面的方式,通过POST 往/students/
发送一个列表:
这里有个坑,可能会碰到AttributeError: 'ListSerializer' object has no attribute 'fields'错误。
这是响应数据格式的问题。没关系。刷新页面即可。
也可以在POSTMAN中进行测试,就不会出现这个问题。
批量删除
先上代码:
from rest_framework.viewsets import ModelViewSet from .serializers import StudentSerializer, ClassroomSerializer from .models import Student, Classroom from rest_framework import status from rest_framework.response import Response from django.shortcuts import get_object_or_404 from rest_framework.decorators import action class StudentViewSet(ModelViewSet): serializer_class = StudentSerializer queryset = Student.objects.all() # 通过many=True直接改造原有的API,使其可以批量创建 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) # 新增一个批量删除的API。删除单个对象,依然建议使用原API # 通过DELETE访问访问url domain.com/students/multiple_delete/?pks=4,5 @action(methods=['delete'], detail=False) def multiple_delete(self, request, *args, **kwargs): # 获取要删除的对象们的主键值 pks = request.query_params.get('pks', None) if not pks: return Response(status=status.HTTP_404_NOT_FOUND) for pk in pks.split(','): get_object_or_404(Student, id=int(pk)).delete() return Response(status=status.HTTP_204_NO_CONTENT)
要注意,原DRF是通过DELETE/students/1/
删除id为1的学生。
那么如果我想批量删除id为1,3,5的三个数据怎么办?
反正肯定是不能往/students/1/
这样的url发送请求的。
那么是构造一条这样的url吗?/students/1,3,5/
?或者/students/?pk=1,3,5
还是往/students/
发送json数据[1,3,5]
?
这里,我采用/students/multiple_delete/?pks=1,3,5
的形式。
这样,它创建了一条新的接口,既避开了/students/
这个接口,也能通过url发送参数。
由于我们的视图继承的是ModelViewSet,所以需要通过action装饰器,增加一个同名的multiple_delete()方法。
为了防止id和Python内置的id函数冲突。我们这里使用pks作为url的参数名。
通过一个for循环,分割逗号获取批量主键值。
通过主键值去数据库中查找对象,然后删除。(这里只是实现功能,未处理异常)
下面,最好在POSTMAN中测试一下:
注意请求是DELETE /students/multiple_delete/?pks=4,5
再访问/students/
,可以看到相关数据确实被删除了。
批量更新
代码如下:
from rest_framework.viewsets import ModelViewSet from .serializers import StudentSerializer, ClassroomSerializer from .models import Student, Classroom from rest_framework import status from rest_framework.response import Response from django.shortcuts import get_object_or_404 from rest_framework.decorators import action class StudentViewSet(ModelViewSet): serializer_class = StudentSerializer queryset = Student.objects.all() # 通过many=True直接改造原有的API,使其可以批量创建 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) # 新增一个批量删除的API。删除单个对象,依然建议使用原API # 通过DELETE访问访问url domain.com/students/multiple_delete/?pks=4,5 @action(methods=['delete'], detail=False) def multiple_delete(self, request, *args, **kwargs): pks = request.query_params.get('pks', None) if not pks: return Response(status=status.HTTP_404_NOT_FOUND) for pk in pks.split(','): get_object_or_404(Student, id=int(pk)).delete() return Response(status=status.HTTP_204_NO_CONTENT) # 新增一个批量修改的API。更新单个对象,依然建议使用原API # 通过PUT方法访问url domain.com/students/multiple_update/ # 发送json格式的数据,数据是个列表,列表中的每一项是个字典,每个字典是一个实例 @action(methods=['put'], detail=False) def multiple_update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instances = [] # 这个变量是用于保存修改过后的对象,返回给前端 for item in request.data: # 遍历列表中的每个对象字典 instance = get_object_or_404(Student, id=int(item['id'])) # 通过ORM查找实例 # 构造序列化对象,注意partial=True表示允许局部更新 # 由于我们前面重写了get_serializer方法,进行了many=True的判断。 # 但此处不需要many=True的判断,所以必须调用父类的get_serializer方法 serializer = super().get_serializer(instance, data=item, partial=partial) serializer.is_valid(raise_exception=True) serializer.save() instances.append(serializer.data) # 将数据添加到列表中 return Response(instances)
更新和删除不同的地方在于,它在提供主键值的同时,还需要提供新的字段值。
所以,这里我们将主键值放在json数据中,而不是作为url的参数。
请仔细阅读上面的代码注释。
这里有个小技巧,其实可以根据HTTP的PUT和PATCH的不同,灵活设定partial参数的值。
另外,要注意的对get_serializer()方法的处理。
下面测试一下。在POSTMAN中通过PUT方法,访问/students/multiple_update/
,并携带如下的json数据:
[ { "id":2, "name":"tom", "classroom":3 }, { "id":3, "name":"jack", "classroom":2 } ]
上面是整体更新,局部更新也是可以的。
djangorestframework-bulk
前面,我们通过蹩脚的代码,实现了最基础的批量增删改查。
但问题太多,不够优雅清晰、异常未处理、边界未考虑等等,实在是太烂。
事实上,有这么个djangorestframework-bulk库,已经高水平地实现了我们的需求。
这个库非常简单,核心的其实只有3个模块,核心代码也就300行左右,非常短小精干,建议精读它的源码,肯定会有收获。
官网:https://pypi.org/project/djangorestframework-bulk/
github:https://github.com/miki725/django-rest-framework-bulk
最后更新:2015年4月
最后版本:0.2.1
它有两个序列化器的版本:drf2\drf3。我们用drf3。
依赖
-
Python > = 2.7
-
的Django > = 1.3
-
Django REST framework > = 3.0.0
安装
使用pip:
$ pip install djangorestframework-bulk
范例
视图
我们注释掉前面章节中的代码,编写下面的代码,使用bulk库来实现批量操作。
bulk中的views(和mixins)非常类似drf原生的generic views(和mixins)
from rest_framework.serializers import ModelSerializer from .models import Student from rest_framework_bulk import ( BulkListSerializer, BulkSerializerMixin, BulkModelViewSet ) from rest_framework.filters import SearchFilter # 序列化器。暂时写在视图模块里 # 必须先继承BulkSerializerMixin,由它将只读字段id的值写回到validated_data中,才能实现更新操作。 class StudentSerializer(BulkSerializerMixin, ModelSerializer): class Meta(object): model = Student fields = '__all__' # 在Meta类下面的list_serializer_class选项用来修改当`many=True`时使用的类。 # 默认情况下,DRF使用的是ListSerializer。 # 但是ListSerializer没有实现自己的批量update方法。 # 在DRF3中如果需要批量更新对象,则需定义此属性,并编写ListSerializer的子类 # 所以bulk库提供了一个BulkListSerializer类 # 它直接继承了ListSerializer,并重写了update方法。 list_serializer_class = BulkListSerializer # 这条可以不写。但实际上,批量删除需要搭配过滤操作 filter_backends = (SearchFilter,) # 视图集 class StudentView(BulkModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer def allow_bulk_destroy(self, qs, filtered): # 这里作为例子,简单粗暴地直接允许批量删除 return True
然后我们将自动获得下面的功能:
# 批量查询 GET http://127.0.0.1/students/ # 创建单个对象 POST http://127.0.0.1/students/ body {"field":"value","field2":"value2"} 发送字典格式的json数据 # 创建多个对象 POST http://127.0.0.1/students/ body [{"field":"value","field2":"value2"}] 发送列表格式的json数据 # 更新多个对象(需要提供所有字段的值) PUT http://127.0.0.1/students/ body [{"field":"value","field2":"value2"}] 发送列表格式的json数据 # 局部更新多个对象(不需要提供所有字段的值) PATCH http://127.0.0.1/students/ body [{"field":"value"}] 发送列表格式的json数据 # 删除多个对象 DELETE http://127.0.0.1/students/
当然,原生的单个对象的操作也是依然支持的!
要特别注意DELETE操作,这个例子里会直接将所有的数据全部删除。如果你想删除指定的一批数据,可以搭配filter_backends
来过滤查询集,使用allow_bulk_destroy
方法来自定义删除策略。
可以看到bulk库对于RESTful的url没有任何改动,非常优雅,比我们上面的蹩脚方法强太多。
路由
路由也需要修改一下。
bulk的路由可以自动映射批量操作,它对DRF原生的DefaultRouter进行了简单的封装:
from rest_framework_bulk.routes import BulkRouter from .views import StudentView router = BulkRouter() router.register(r'students', StudentView) urlpatterns = router.urls
测试
现在可以测试一下。下面提供一部分测试数据:
[ { "name": "s1", "classroom": 1 }, { "name": "s2", "classroom": 3 }, { "name": "s3", "classroom": 2 } ]
- 建议在POSTMAN中进行测试
- PUT和PATCH要携带id值
- PUT要携带所有字段的值
- PATCH可以只携带要更新的字段的值
- DELETE一定要小心
可以看到功能完全实现,批量操作成功。
DRF3相关
DRF3的API相比DRF2具有很多变化,尤其是在序列化器上。要在DRF3上使用bulk,需要注意以下几点:
-
如果你的视图需要批量更新功能,则必须指定
list_serializer_class
(也就是继承了BulkUpdateModelMixin
时) -
DRF3 从
serializer.validated_data
中移除了只读字段。所以,无法关联validated_data
和ListSerializer
,因为缺少模型主键这个只读字段。为了解决这个问题,你必须在你的序列化类中使用BulkSerializerMixin
,这个混入类会添加模型主键字段到validated_data
中。默认情况,模型主键是id
,你可以通过update_lookup_field
属性来指定主键名:class FooSerializer(BulkSerializerMixin, ModelSerializer): class Meta(object): model = FooModel list_serializer_class = BulkListSerializer update_lookup_field = 'slug'
注意事项
大多数API的每种资源都有两个级别的url:
url(r'foo/', ...)
url(r'foo/(?P<pk>\d+)/', ...)
但是,第二个URL不适用于批量操作,因为该URL直接映射到单个资源。因此,所有批量通用视图仅适用于第一个URL。
如果只需要某个单独的批量操作功能,bulk提供了多个通用视图类。例如,ListBulkCreateAPIView
将仅执行批量创建操作。有关可用的通用视图类的完整列表,请访问generics.py
的源代码。
大多数批量操作都是安全的,因为数据都是和每个对象关联的。例如,如果您需要更新3个特定资源,则必须在PUT
或PATCH
的请求数据中明确的标识出那些资源的id。唯一的例外是批量删除,例如对第一种URL的DELETE
请求可能会删除所有资源,而无需任何特殊确认。为了解决这个问题,批量删除混入类中提供了一个钩子,以确定是否应允许执行该批量删除请求,也就是allow_bulk_destroy
方法:
class FooView(BulkDestroyAPIView): def allow_bulk_destroy(self, qs, filtered): # 你的自定义业务逻辑写在这里 # qs参数是一个查询集,它来自self.get_queryset() # 默认要检查qs是否被过滤了。 # filtered参数来自self.filter_queryset(qs) return qs is not filtered # 最终返回True,则执行删除操作。返回False,则不执行。
默认情况下,allow_bulk_destroy
方法会检查查询集是否已过滤,如果没有过滤,则不允许执行该批量删除操作。此处的逻辑是,你知道自己在删除哪些对象,知道自己没有进行全部对象的删除操作。通俗地说就是,程序员对你的代码在作什么,心里要有数。
源码解读
下图是目录组织结构。分drf2和drf3,基本使用drf3。test目录我们不关心。
核心其实就是根目录下的5个模块和drf3目录。其中的models.py文件是空的,没有代码。
__init__.py
这个模块就是简单地导入其它模块:
__version__ = '0.2.1' __author__ = 'Miroslav Shubernetskiy' try: from .generics import * # noqa from .mixins import * # noqa from .serializers import * # noqa except Exception: pass
#NOQA 注释的作用是告诉PEP8规范检测工具,这个地方不需要检测。
也可以在一个文件的第一行增加 #flake8:NOQA 来告诉规范检测工具,这个文件不用检查。
serializers.py
源代码:
# 这是用于Python版本兼容,print方法和Unicode字符 from __future__ import print_function, unicode_literals import rest_framework if str(rest_framework.__version__).startswith('2'): from .drf2.serializers import * # noqa else: from .drf3.serializers import * # noqa
就是针对不同的DRF版本,导入不同的serializers。
mixins.py
源代码:
from __future__ import print_function, unicode_literals import rest_framework if str(rest_framework.__version__).startswith('2'): from .drf2.mixins import * # noqa else: from .drf3.mixins import * # noqa
和serializers.py类似,针对不同的DRF版本,导入不同的mixins。
routes.py
搭配bulk的BulkModelViewSet视图类进行工作。
源代码:
from __future__ import unicode_literals, print_function import copy from rest_framework.routers import DefaultRouter, SimpleRouter __all__ = [ 'BulkRouter', ] class BulkRouter(DefaultRouter): """ 将http的method映射到bulk的minxins中的处理函数 """ routes = copy.deepcopy(SimpleRouter.routes) routes[0].mapping.update({ 'put': 'bulk_update', 'patch': 'partial_bulk_update', 'delete': 'bulk_destroy', })
对DRF原生的DefaultRouter路由模块进行再次封装,主要是修改三个HTTP方法的映射关系,将它们映射到bulk库的mixins方法。
generics.py
这个模块的风格和DRF的源码非常类似,都是各种继承搭配出来各种类视图。
里面混用了DRF原生的mixin和bulk自己写的mixin。
主要是将http的method映射到视图类中对应的处理方法。
源代码:
from __future__ import unicode_literals, print_function from rest_framework import mixins from rest_framework.generics import GenericAPIView from rest_framework.viewsets import ModelViewSet from . import mixins as bulk_mixins __all__ = [ 'BulkCreateAPIView', 'BulkDestroyAPIView', 'BulkModelViewSet', 'BulkUpdateAPIView', 'ListBulkCreateAPIView', 'ListBulkCreateDestroyAPIView', 'ListBulkCreateUpdateAPIView', 'ListBulkCreateUpdateDestroyAPIView', 'ListCreateBulkUpdateAPIView', 'ListCreateBulkUpdateDestroyAPIView', ] # ################################################## # # 下面是一些具体的视图类。通过将mixin类与基视图组合来提供方法处理程序。 # 基本前面继承一堆mixins,后面继承GenericAPIView # ################################################## # # 批量创建 class BulkCreateAPIView(bulk_mixins.BulkCreateModelMixin, GenericAPIView): def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 批量更新(局部和整体) class BulkUpdateAPIView(bulk_mixins.BulkUpdateModelMixin, GenericAPIView): def put(self, request, *args, **kwargs): return self.bulk_update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_bulk_update(request, *args, **kwargs) # 批量删除 class BulkDestroyAPIView(bulk_mixins.BulkDestroyModelMixin, GenericAPIView): def delete(self, request, *args, **kwargs): return self.bulk_destroy(request, *args, **kwargs) # 批量查看和创建 # 注意批量查看依然使用的是DRF原生的ListModelMixin提供的功能 class ListBulkCreateAPIView(mixins.ListModelMixin, bulk_mixins.BulkCreateModelMixin, GenericAPIView): def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 批量查看、单个创建、批量更新 class ListCreateBulkUpdateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, bulk_mixins.BulkUpdateModelMixin, GenericAPIView): def get(self, 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.bulk_update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_bulk_update(request, *args, **kwargs) class ListCreateBulkUpdateDestroyAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, bulk_mixins.BulkUpdateModelMixin, bulk_mixins.BulkDestroyModelMixin, GenericAPIView): def get(self, 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.bulk_update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_bulk_update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.bulk_destroy(request, *args, **kwargs) class ListBulkCreateUpdateAPIView(mixins.ListModelMixin, bulk_mixins.BulkCreateModelMixin, bulk_mixins.BulkUpdateModelMixin, GenericAPIView): def get(self, 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.bulk_update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_bulk_update(request, *args, **kwargs) class ListBulkCreateDestroyAPIView(mixins.ListModelMixin, bulk_mixins.BulkCreateModelMixin, bulk_mixins.BulkDestroyModelMixin, GenericAPIView): def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.bulk_destroy(request, *args, **kwargs) # 这个功能最全面 class ListBulkCreateUpdateDestroyAPIView(mixins.ListModelMixin, bulk_mixins.BulkCreateModelMixin, bulk_mixins.BulkUpdateModelMixin, bulk_mixins.BulkDestroyModelMixin, GenericAPIView): def get(self, 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.bulk_update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_bulk_update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.bulk_destroy(request, *args, **kwargs) # ########################################################## # # 专门提供的一个viewset,搭配了批量创建、更新和删除功能 # 它需要搭配bulk的router模块使用。 # 如果不用这个,就用ListBulkCreateUpdateDestroyAPIView # ########################################################## # class BulkModelViewSet(bulk_mixins.BulkCreateModelMixin, bulk_mixins.BulkUpdateModelMixin, bulk_mixins.BulkDestroyModelMixin, ModelViewSet): pass
drf3/mixins.py
这个模块实现了核心的业务逻辑。请注意阅读源代码中的注释。
源代码:
from __future__ import print_function, unicode_literals from rest_framework import status from rest_framework.mixins import CreateModelMixin from rest_framework.response import Response __all__ = [ 'BulkCreateModelMixin', 'BulkDestroyModelMixin', 'BulkUpdateModelMixin', ] class BulkCreateModelMixin(CreateModelMixin): """ Django REST >= 2.2.5.以后的版本多了一个many=True的参数。 通过这个参数,可以实现单个和批量创建实例的统一操作。 其本质是使用DRF提供的ListSerializer类 """ # 重写create方法 def create(self, request, *args, **kwargs): # 通过判断request.data变量是列表还是字典,来区分是单体操作还是批量操作。 # 这要求我们前端发送json格式的数据时,必须定义好数据格式 bulk = isinstance(request.data, list) if not bulk: # 如果不是批量操作,则调用父类的单体创建方法 return super(BulkCreateModelMixin, self).create(request, *args, **kwargs) else: # 如果是批量操作,则添加many=True参数 serializer = self.get_serializer(data=request.data, many=True) serializer.is_valid(raise_exception=True) # 这里少了DRF源码中的headers = self.get_success_headers(serializer.data) self.perform_bulk_create(serializer) return Response(serializer.data, status=status.HTTP_201_CREATED) # 这是个钩子方法 def perform_bulk_create(self, serializer): return self.perform_create(serializer) class BulkUpdateModelMixin(object): """ 同样是通过many=True参数来实现批量更新 """ # 重写单个对象的获取 def get_object(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field # 这个if执行的是父类的操作 if lookup_url_kwarg in self.kwargs: return super(BulkUpdateModelMixin, self).get_object() # 如果没有携带id,则直接返回,什么都不做。 # 也就是 PUT http://127.0.0.1/students/ # 和 PUT http://127.0.0.1/students/1/的区别 return # 核心的更新方法 def bulk_update(self, request, *args, **kwargs): # 先看看是PUT还是PATCH partial = kwargs.pop('partial', False) # 限制只对过滤后的查询集进行更新 # 下面的代码就是基本的DRF反序列化套路 # 核心是instances是个过滤集,many指定为True,partial根据方法来变 # 这里的逻辑是将单体更新当作只有一个元素的列表来更新(也就是批量为1)。 serializer = self.get_serializer( self.filter_queryset(self.get_queryset()), data=request.data, many=True, partial=partial, ) serializer.is_valid(raise_exception=True) self.perform_bulk_update(serializer) return Response(serializer.data, status=status.HTTP_200_OK) # 如果是PATCH方法,则手动添加partial=True参数,表示局部更新 # 实际执行的方法和整体更新一样,都是调用bulk_update方法 def partial_bulk_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.bulk_update(request, *args, **kwargs) # 钩子方法 def perform_update(self, serializer): serializer.save() # 钩子方法 def perform_bulk_update(self, serializer): return self.perform_update(serializer) # 删除操作 class BulkDestroyModelMixin(object): """ 用于删除模型实例 """ def allow_bulk_destroy(self, qs, filtered): """ 这是一个钩子,用于确保批量删除操作是安全的。 默认情况下,它会检查删除操作是否在一个过滤集上进行,不能对原始查询集也就是qs进行删除。 最终的返回值是布尔值,如果返回True,表示允许删除,否则拒绝。 源码这里是简单地比较了qs和filtered是否相同,你可以自定义判断逻辑。 删除操作可以配合过滤后端。 """ return qs is not filtered # DELETE方法将被转发到这里 def bulk_destroy(self, request, *args, **kwargs): # 首先,获取查询集 qs = self.get_queryset() # 获取过滤集 filtered = self.filter_queryset(qs) # 调用allow_bulk_destroy方法,判断是否允许该删除操作 if not self.allow_bulk_destroy(qs, filtered): # 如果不允许,返回400响应,错误的请求 return Response(status=status.HTTP_400_BAD_REQUEST) # 否则对过滤集执行批量删除操作 self.perform_bulk_destroy(filtered) return Response(status=status.HTTP_204_NO_CONTENT) # 这个删除方法,其实就是ORM的delete方法 # 之所以设置这个方法,其实就是个钩子,方便我们自定义 def perform_destroy(self, instance): instance.delete() # 批量删除很简单,就是遍历过滤集,逐个删除 def perform_bulk_destroy(self, objects): for obj in objects: self.perform_destroy(obj)
drf3/serializers.py
这个模块只有两个类,它们提供了2个功能。
- BulkSerializerMixin:往验证后的数据中添加主键字段的值
- BulkListSerializer:提供批量更新的update方法
源代码:
from __future__ import print_function, unicode_literals import inspect # Python内置模块。从活动的Python对象获取有用的信息。 from rest_framework.exceptions import ValidationError from rest_framework.serializers import ListSerializer __all__ = [ 'BulkListSerializer', 'BulkSerializerMixin', ] # 由于DRF源码在默认情况下,会将只读字段的值去掉,所以id主键值不会出现在validated_data中 # 因为我们现在需要批量更新对象,url中也没有携带对象的id,所以我们需要手动将id的值添加回去。 class BulkSerializerMixin(object): # 由外部数据转换为Python内部字典 def to_internal_value(self, data): # 先调用父类的方法,获得返回值 ret = super(BulkSerializerMixin, self).to_internal_value(data) # 去Meta元类中看看,有没有指定'update_lookup_field'属性,如果没有,默认使用id # 这本质就是个钩子,允许我们自定义主键字段 id_attr = getattr(self.Meta, 'update_lookup_field', 'id') # 获取当前请求的类型 request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '') # 如果下面的三个条件都满足: # self.root是BulkListSerializer的实例 # id_attr变量不为空 # 请求的方法是'PUT'或'PATCH' # 那么执行if语句中的代码 if all((isinstance(self.root, BulkListSerializer), id_attr, request_method in ('PUT', 'PATCH'))): # 拿到id字段的句柄 id_field = self.fields[id_attr] # 拿到字段的值 id_value = id_field.get_value(data) # 为ret追加键值对 ret[id_attr] = id_value return ret # 这个类主要是在ListSerializer基础上重写的update逻辑,实现批量操作 class BulkListSerializer(ListSerializer): # 指定用于更新的查询字段为id update_lookup_field = 'id' def update(self, queryset, all_validated_data): # 先看看有没有指定用于查询的字段 id_attr = getattr(self.child.Meta, 'update_lookup_field', 'id') # 通过id去获取所有的键值对 # 下面是一个字典推导式 all_validated_data_by_id = { i.pop(id_attr): i for i in all_validated_data } # 对数据类型做判断 if not all((bool(i) and not inspect.isclass(i) for i in all_validated_data_by_id.keys())): raise ValidationError('') # 使用ORM从查询集中过滤出那些需要更新的模型实例 # 比如id__in=[1,3,4] objects_to_update = queryset.filter(**{ '{}__in'.format(id_attr): all_validated_data_by_id.keys(), }) # 如果过滤出来的模型实例数量和用于更新的数据数量不一致,弹出异常 if len(all_validated_data_by_id) != objects_to_update.count(): raise ValidationError('Could not find all objects to update.') # 准备一个空列表,用于保存将要被更新的实例 updated_objects = [] # 循环每个实例 for obj in objects_to_update: obj_id = getattr(obj, id_attr) obj_validated_data = all_validated_data_by_id.get(obj_id) # 使用模型序列化器的update方法进行实际的更新动作,以防update方法在别的地方被覆盖 updated_objects.append(self.child.update(obj, obj_validated_data)) return updated_objects
机械表受磁是快还是慢
机械表保养会被偷换零件吗
机械手表保养一次费用
这篇关于深度解析Django REST Framework 批量操作的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-20go-zero 框架的 RPC 服务 启动start和停止 底层是怎么实现的?-icode9专业技术文章分享
- 2024-12-19Go-Zero 框架的 RPC 服务启动和停止的基本机制和过程是怎么实现的?-icode9专业技术文章分享
- 2024-12-18怎么在golang中使用gRPC测试mock数据?-icode9专业技术文章分享
- 2024-12-15掌握PageRank算法核心!你离Google优化高手只差一步!
- 2024-12-15GORM 中的标签 gorm:"index"是什么?-icode9专业技术文章分享
- 2024-12-11怎么在 Go 语言中获取 Open vSwitch (OVS) 的桥接信息(Bridge)?-icode9专业技术文章分享
- 2024-12-11怎么用Go 语言的库来与 Open vSwitch 进行交互?-icode9专业技术文章分享
- 2024-12-11怎么在 go-zero 项目中发送阿里云短信?-icode9专业技术文章分享
- 2024-12-11怎么使用阿里云 Go SDK (alibaba-cloud-sdk-go) 发送短信?-icode9专业技术文章分享
- 2024-12-10搭建个人博客网站之一、使用hugo创建个人博客网站