django-rest-framework:在ModelViewSet中添加批量操作

时间:2019-08-28 15:46:59

标签: python django django-rest-framework django-rest-viewsets

我有许多端点使用ModelViewSet管理模型的CRUD操作。

我想要做的是在这些相同的端点上添加批量创建,更新和删除。换句话说,我想将POSTPUTPATCHDELETE添加到集合端点(例如:/api/v1/my-model)。有一个django-rest-framework-bulk软件包可用,但似乎已被放弃(已经有4年没有更新了),在生产中不再使用该软件包的情况下,我感到不舒服。

此外,这里有几个类似的问题,都有解决方案,以及我发现的博客文章。但是,他们似乎都使用了基础ViewSetAPIView,这将需要重写我现有的所有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

有想法吗?

1 个答案:

答案 0 :(得分:1)

我知道您说过您想避免添加额外的操作,但是我认为这是更新现有视图以进行批量创建/更新/删除的最简单方法。

您可以创建一个添加到视图中的混合器,以处理所有内容,只需在现有视图和序列化器中更改一行即可。

假设您的ListSerializerDRF 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架构文档。