Reputation: 1853
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?
Upvotes: 23
Views: 31858
Reputation: 515
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)
Upvotes: 0
Reputation: 795
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
Upvotes: 0
Reputation: 3647
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.
Upvotes: 1
Reputation: 1589
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)
Upvotes: 26
Reputation: 818
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)
Upvotes: 6
Reputation: 10167
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.
Upvotes: 4