How can I PUT/POST JSON data to a ListSerializer?

12,340

Django REST framework by default assumes that you are not dealing with bulk data creation, updates, or deletion. This is because 99% of people are not dealing with bulk data creation, and DRF leaves the other 1% to third-party libraries.

In Django REST framework 2.x and 3.x, a third party package exists for this.

Now, you are trying to do bulk creation but you are getting an error back that says

Invalid data. Expected a dictionary, but got list

This is because you are sending in a list of objects to create, instead of just sending in one. You can get around this a few ways, but the easiest is to just override get_serializer on your view to add the many=True flag to the serializer when it is a list.

def get_serializer(self, *args, **kwargs):
    if "data" in kwargs:
        data = kwargs["data"]

        if isinstance(data, list):
            kwargs["many"] = True

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

This will allow Django REST framework to know to automatically use the ListSerializer when creating objects in bulk. Now, for other operations such as updating and deleting, you are going to need to override the default routes. I'm going to assume that you are using the routes provided by Django REST framework bulk, but you are free to use whatever method names you want.

You are going to need to add methods for bulk PUT and PATCH to the view as well.

from rest_framework.response import Response

def bulk_update(self, request, *args, **kwargs):
    partial = kwargs.pop("partial", False)

    queryset = self.filter_queryset(self.get_queryset))

    serializer = self.get_serializer(instance=queryset, data=request.data, many=True)
    serializer.is_valid(raise_exception=True)

    self.perform_update(serializer)

    return Response(serializer.data)

def partial_bulk_update(self, *args, **kwargs):
    kargs["partial"] = True
    return super(MyView, self).bulk_update(*args, **kwargs)

This won't work out of the box as Django REST framework doesn't support bulk updates by default. This means you also have to implement your own bulk updates. The current code will handle bulk updates as though you are trying to update the entire list, which is how the old bulk updating package previously worked.

While you didn't ask for bulk deletion, that wouldn't be particularly difficult to do.

def bulk_delete(self, request, *args, **kwargs):
    queryset = self.filter_queryset(self.get_queryset())
    self.perform_delete(queryset)
    return Response(status=204)

This has the same effect of removing all objects, the same as the old bulk plugin.

None of this code was tested. If it doesn't work, consider it as a detailed example.

Share:
12,340

Related videos on Youtube

petroleyum
Author by

petroleyum

Updated on September 26, 2022

Comments

  • petroleyum
    petroleyum over 1 year

    I'm reading about customizing multiple update here and I haven't figured out in what case the custom ListSerializer update method is called. I would like to update multiple objects at once, I'm not worried about multiple create or delete at the moment.

    From the example in the docs:

    # serializers.py
    class BookListSerializer(serializers.ListSerializer):
        def update(self, instance, validated_data):
            # custom update logic
            ...
    
    class BookSerializer(serializers.Serializer):
        ...
        class Meta:
            list_serializer_class = BookListSerializer
    

    And my ViewSet

    # api.py
    class BookViewSet(ModelViewSet):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    

    And my url setup using DefaultRouter

    # urls.py
    router = routers.DefaultRouter()
    router.register(r'Book', BookViewSet)
    
    urlpatterns = patterns('',                       
        url(r'^api/', include(router.urls)),
        ...
    

    So I have this set up using the DefaultRouter so that /api/Book/ will use the BookSerializer.

    Is the general idea that if I POST/PUT/PATCH an array of JSON objects to /api/Book/ then the serializer should switch over to BookListSerializer?

    I've tried POST/PUT/PATCH JSON data list to this /api/Book/ that looks like:

    [ {id:1,title:thing1}, {id:2, title:thing2} ]
    

    but it seems to still treat the data using BookSerializer instead of BookListSerializer. If I submit via POST I get Invalid data. Expected a dictionary, but got list. and if I submit via PATCH or PUT then I get a Method 'PATCH' not allowed error.

    Question: Do I have to adjust the allowed_methods of the DefaultRouter or the BookViewSet to allow POST/PATCH/PUT of lists? Are the generic views not set up to work with the ListSerializer?

    I know I could write my own list deserializer for this, but I'm trying to stay up to date with the new features in DRF 3 and it looks like this should work but I'm just missing some convention or some option.

  • petroleyum
    petroleyum over 9 years
    Thanks. Fixed that. Now I'm submitting a list to /api/Book/. If I submit via POST I get Invalid data. Expected a dictionary, but got list. and if I submit via PATCH or PUT then I get a Method 'PATCH' not allowed.. I'm using the DefaultRouter and a ModelViewSet if that helps.
  • Kevin Brown-Silva
    Kevin Brown-Silva over 9 years
    I've updated the answer to address your question in detail. Hopefully it covers the multiple parts to your question. With any luck, the third party package will be updated to support 3.0 in the near future.
  • petroleyum
    petroleyum over 9 years
    Awesome, thank you. I guess my question could have been boiled down to "It looks like DRF has some default bulk update behavior now with 3.0?" and the answer is "Not quite." Your code example is exactly what I needed, I'll test it out and update if necessary.
  • Quentin Donnellan
    Quentin Donnellan over 9 years
    Thanks a bunch for this Kevin - this fixes things for us here: github.com/burke-software/django-sis/commit/…
  • miki725
    miki725 about 9 years
    Updated the answer that DRF-bulk now support both DRF2 and DRF3! - github.com/miki725/django-rest-framework-bulk
  • Sassan
    Sassan about 7 years
    DRF-bulk is not maintained anymore. What's current canonical solution for this?
  • Kevin Brown-Silva
    Kevin Brown-Silva about 7 years
    @sassan up until 3.4.x it worked fine, you may want to consider creating a ticket with them about any recent issues
  • Sassan
    Sassan about 7 years
    Didn't try it when I saw last commit is 2 years ago. Now I'll try it. Thanks for quick response.
  • Greg Holst
    Greg Holst over 4 years
    I found a solution that's less complicated (at least for me as a Django beginner) stackoverflow.com/a/59756993/7392069
  • cavalcantelucas
    cavalcantelucas almost 3 years
    Than you for the excelent explanation! I'd wish to vote twice on it!