Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bulk, partial updates with Django Rest Framework

I want to partially-update multiple items at once. I've already added a mixin for allowing me to bulk-create (and that works well) but even though I've added a partial argument, it doesn't allow for patching a list.

I'm guessing that this is a routing issue. I need a new view to handle PATCH on / (rather than /id/), but I'm well out of my depth.

Existing answers for this don't work for 3.8, or at least haven't worked for me. What do I need to do to the following?

class ResourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Resource
        fields = ('id', 'name', ...)
        read_only_fields = ('id',)

 class BulkMixin:
     def get_serializer(self, *args, **kwargs):
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True
            kwargs['partial'] = True

        return super().get_serializer(*args, **kwargs)

class ResourceViewSet(BulkMixin, viewsets.ModelViewSet):
    serializer_class = ResourceSerializer
like image 957
Oli Avatar asked Nov 03 '18 09:11

Oli


People also ask

What is partial update in Django?

Generally speaking, partial is used to check whether the fields in the model is needed to do field validation when client submitting data to the view. For example, we have a Book model like this, pls note both of the name and author_name fields are mandatory (not null & not blank).


Video Answer


2 Answers

After ~10 hours banging my head against this wall, I decided that the right way could take a back seat and I'd make do with the hack way that works. I added the following gnarlfest to my viewset.

from rest_framework.decorators import action

@action(methods=['patch'], detail=False)
def bulk_update(self, request):

    data = {  # we need to separate out the id from the data
        i['id']: {k: v for k, v in i.items() if k != 'id'}
        for i in request.data
    }

    for inst in self.get_queryset().filter(id__in=data.keys()):
        serializer = self.get_serializer(inst, data=data[inst.id], partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

    return Response({})

If I patch in a list of [{id: 123, otherfield: "abc"}, ...] this will now bulk-partial update. I'm fairly sure this is doing n+1 queries and that it'd be a lot more efficient in raw ORM... but for now, it's infinitely better than n requests. Also, if the ID isn't in the queryset, it will pass over rather than erroring. That works for me, but may not for others.

I'll stick a bounty on this question in 48h to tempt in some good answers.

like image 136
Oli Avatar answered Oct 10 '22 16:10

Oli


I would suggest not trying to implement this yourself. Consider using django-rest-framework-bulk. It supports bulk, partial updates and provides serializers, views, and a router to make things really simple to setup.

like image 33
Levi Payne Avatar answered Oct 10 '22 18:10

Levi Payne