我有许多端点使用ModelViewSet
管理模型的CRUD操作。
我想要做的是在这些相同的端点上添加批量创建,更新和删除。换句话说,我想将POST
,PUT
,PATCH
和DELETE
添加到集合端点(例如:/api/v1/my-model
)。有一个django-rest-framework-bulk
软件包可用,但似乎已被放弃(已经有4年没有更新了),在生产中不再使用该软件包的情况下,我感到不舒服。
此外,这里有几个类似的问题,都有解决方案,以及我发现的博客文章。但是,他们似乎都使用了基础ViewSet
或APIView
,这将需要重写我现有的所有ModelViewSet
代码。
最后,可以选择使用@action
装饰器,但是这需要我有一个单独的列表端点(例如-/api/v1/my-model/bulk
),我要避免。
在保持现有ModelViewSet
视图的同时,还有其他方法可以做到这一点吗?我一直在研究GenericViewSet
和mixin,想知道是否可以创建自己的mixin。但是,查看mixin代码后,您似乎无法指定要附加到给定mixin的HTTP Request方法。
最后,我尝试创建一个单独的ViewSet
来接受PUT并将其添加到我的URL中,但这是行不通的(尝试将PUT设置为{{1}时,我得到405方法不被允许}。我尝试过的代码如下:
/api/v1/my-model
有想法吗?
答案 0 :(得分:1)
我知道您说过您想避免添加额外的操作,但是我认为这是更新现有视图以进行批量创建/更新/删除的最简单方法。
您可以创建一个添加到视图中的混合器,以处理所有内容,只需在现有视图和序列化器中更改一行即可。
假设您的ListSerializer
与DRF documentation相似,那么混合将如下所示。
core / serializers.py
class BulkUpdateSerializerMixin:
"""
Mixin to be used with BulkUpdateListSerializer & BulkUpdateRouteMixin
that adds the ID back to the internal value from the raw input data so
that it's included in the validated data.
"""
def passes_test(self):
# Must be an update method for the ID to be added to validated data
test = self.context['request'].method in ('PUT', 'PATCH')
test &= self.context.get('bulk_update', False)
return test
def to_internal_value(self, data):
ret = super().to_internal_value(data)
if self.passes_test():
ret['id'] = self.fields['id'].get_value(data)
return ret
core / views.py
class BulkUpdateRouteMixin:
"""
Mixin that adds a `bulk_update` API route to a view set. To be used
with BulkUpdateSerializerMixin & BulkUpdateListSerializer.
"""
def get_object(self):
# Override to return None if the lookup_url_kwargs is not present.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
if lookup_url_kwarg in self.kwargs:
return super().get_object()
return
def get_serializer(self, *args, **kwargs):
# Initialize serializer with `many=True` if the data passed
# to the serializer is a list.
if self.request.method in ('PUT', 'PATCH'):
data = kwargs.get('data', None)
kwargs['many'] = isinstance(data, list)
return super().get_serializer(*args, **kwargs)
def get_serializer_context(self):
# Add `bulk_update` flag to the serializer context so that
# the id field can be added back to the validated data through
# `to_internal_value()`
context = super().get_serializer_context()
if self.action == 'bulk_update':
context['bulk_update'] = True
return context
@action(detail=False, methods=['put'], url_name='bulk_update')
def bulk_update(self, request, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(
queryset,
data=request.data,
many=True,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
然后,您只需从mixins继承
class MyModelSerializer(BulkUpdateSerializerMixin
serializers.ModelSerializer):
class Meta:
model = MyModel
list_serializer_class = BulkUpdateListSerializer
class MyModelViewSet(BulkUpdateRouteMixin,
viewsets.ModelViewSet):
...
您的PUT请求只需要指向'/api/v1/my-model/bulk_update'
更新的混入文件,不需要额外的视图集操作:
对于批量操作,以数据作为列表向列表视图提交POST请求。
class BulkUpdateSerializerMixin:
def passes_test(self):
test = self.context['request'].method in ('POST',)
test &= self.context.get('bulk', False)
return test
def to_internal_value(self, data):
ret = super().to_internal_value(data)
if self.passes_test():
ret['id'] = self.fields['id'].get_value(data)
return ret
在get_serializer()
中进行了检查,以确保仅POST请求可以接受批量操作。如果它是POST且请求数据是列表,则添加一个标志,以便可以将ID字段添加回经过验证的数据,并且您的ListSerializer
可以处理批量操作。
class BulkUpdateViewSetMixin:
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
if self.request.method in ('POST',):
data = kwargs.get('data', None)
is_bulk = isinstance(data, list)
kwargs['many'] = is_bulk
kwargs['context']['bulk'] = is_bulk
return serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
if isinstance(request.data, list):
return self.bulk_update(request)
return super().create(request, *args, **kwargs)
def bulk_update(self, request):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(
queryset,
data=request.data,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
我已经测试了它的工作原理,但是我不知道它将如何影响API架构文档。