25

I am using Django REST framework for API and Angular SPA with Restangular to communicate with the API. Sometimes, I have to add more than one object using the API and I think I can send them together in an array and do this in one request.

I receive wrong input error when I'm trying to add more than one object from the REST framework web interface. I am passing objects or array of objects like below:

// this { "text": "gdhg", },{ "text": "gdhg", },{ "text": "gdhg", }
// or this [{ "text": "gdhg", },{ "text": "gdhg", },{ "text": "gdhg", }]

But I receive ParseError. Where I am wrong and what do I have to change to fix this?

6 Answers 6

27

Another example that supports posting an array as well as posting a single object. Might be useful for anyone else looking for such an example.

class BookViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    ViewSet create and list books

    Usage single : POST
    {
        "name":"Killing Floor: A Jack Reacher Novel", 
        "author":"Lee Child"
    }

    Usage array : POST
    [{  
        "name":"Mr. Mercedes: A Novel (The Bill Hodges Trilogy)",
        "author":"Stephen King"
    },{
        "name":"Killing Floor: A Jack Reacher Novel", 
        "author":"Lee Child"
    }]
    """
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    search_fields = ('name','author')

    def create(self, request, *args, **kwargs):
        """
        #checks if post request data is an array initializes serializer with many=True
        else executes default CreateModelMixin.create function 
        """
        is_many = isinstance(request.data, list)
        if not is_many:
            return super(BookViewSet, self).create(request, *args, **kwargs)
        else:
            serializer = self.get_serializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Sign up to request clarification or add additional context in comments.

1 Comment

Excellent, I was worried I'd need to re-read the documentation to do something like this myself. Thanks to your answer, I didn't need to ;)
6

I am not sure if the problem still exist. But the solution suggested by fiver did not work for me. What works for me is overriding the get_serializer method ONLY.

def get_serializer(self, instance=None, data=None,
                    files=None, many=True, partial=False):
    return super(ViewName, self).get_serializer(instance, data, files,
                                                    many, partial)

If you will notice I am setting default many=True in arguments of get_serializer. Apart from that nothing is required. Overridng of create method is also not required.

Also if you are defining the pre_save and post_save method in the views, expects the list(iterable) as the argument(as you are posting the list) of method not just a single object.

def post_save(self, objects, *args, **kwargs):
    """
    In the post_save, list of obj has been created
    """
    for obj in objects:
        do_something_with(obj)

Comments

4

Here's an example for setting up bulk POSTing in a ListCreateAPIView using the Django REST Framework:

class SomethingList(generics.ListCreateAPIView):
    model = Something
    serializer_class = SomethingSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.DATA, many=True)
        if serializer.is_valid():
            serializer.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The important part here is the many=True argument to the get_serializer() method. Then, to make Angular play nice with this, you can define a service factory as:

.factory('Something', ['$resource', function ($resource) {
    return $resource(
        "url_to_something",
        {},
        {
            save: {
                method: 'POST',
                isArray: true
            }
        }
    );
}])

Where the important part is the isArray: true. If you want to preserve posting single JSON objects, you could change save above to something like saveBulk or similar.

1 Comment

I did that with ModelViewSet and now receiving "'list' object has no attribute 'get'" because the request.DATA is list. How to fix this problem. I am testing it from the web interface of the api with sending json array of objects: [{ "text": "gdhg", },{ "text": "gdhg", },{ "text": "gdhg", }]
1

Building on vibhor's answer:

class ListableViewMixin(object):
    def get_serializer(self, instance=None, data=None, many=False, *args, **kwargs):
        return super(ListableViewMixin, self).get_serializer(
            instance=instance, data=data, many=isinstance(instance, list) or isinstance(data, list),
            *args, **kwargs)

Make your view inherit from this mixin class to automatically determine if a many=True serializer should be used.

Comments

0

If you want to post a list you have to pass in JSON encoded data.

headers = {"Token": "35754sr7cvd7ryh454"}

recipients = [{'name': 'Ut est sed sed ipsa', 
               'email': '[email protected]', 
               'group': 'signers'},
              {'name': 'Development Ltda.', 
               'email': '[email protected]',
               'group': 'signers'}
             ]

requests.post(url, json=recipients, headers=headers)

requests.post(url, json=recipients, headers=headers)

Use json keyword argument (not data) so the data is encoded to JSON and the Content-Type header is set to application/json.

By default, Django Rest Framework assumes you are passing it a single object. To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.

To do it, you'll have to override the .create() method of your view:

def create(self, request, *args, **kwargs):
    many = True if isinstance(request.data, list) else False

    serializer = self.get_serializer(data=request.data, many=many)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Documentation: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects

Comments

0

I looked at all the solutions here, and most have one thing in common which is if the data is a list, then call get_serializer with many=True. Most of the solutions override the create method to do this, but I find it more concise to override get_serializer instead. Way less code, and it makes sense that the get_serializer should understand that list will always mean many=True. There is at least one other answer that touched on this but here is the python3 / 2024 version.

def get_serializer(self, *args, **kwargs):
    if isinstance(kwargs.get("data"), list):
        kwargs["many"] = True

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.