Reputation: 711
I'm trying to create an object with a serializer from a post request but I'm getting an error while trying to pass a list of objects to a nested serializer. While passing the ('id', 'name', 'description')
data in a JSON format works just fine the list of bars
is not getting parsed properly and return the following error :
{'bars' : [ErrorDetail(string='This field is required.', code='required')]}}
Those are the Serializers :
class BarsSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ('name', 'foo')
class FooSerializer(serializers.ModelSerializer):
bars = BarsSerializer(many=True)
class Meta:
model = Foo
fields = ('id', 'author' 'name', 'description', 'bars')
def create(self, validated_data):
validated_data['author'] = self.context['request'].user
# Foo object manager is tested and works
return Foo.objects.create(**validated_data)
This is my request payload :
{
'name': "A Foo",
'description': "A happy foo running in the woods of Central Park",
'bars': [
{name : 'a'},
{name : 'b'},
{name : 'c'},
]
}
Those are the models
class Bar(models.Model):
name = models.CharField(
max_length=255,
default=""
)
foo = models.ForeignKey(
Foo,
related_name='foos',
on_delete=models.CASCADE
)
class Foo(models.Model):
name = models.CharField(
max_length=255
)
description = models.CharField(
max_length=1023,
default=""
)
author = models.ForeignKey(CommonUser, on_delete=models.SET_NULL, null=True)
the problem is only there while testing with Django python manage.py test
and not there while testing the request with postman with the local server
data = {
"name": "A Foo",
"description": "A happy foo running in the woods of Central Park",
"bars": [
{name : "a"},
{name : "b"},
{name : "c"},
]
}
res = self.client_one.post(reverse('foo-list'), data)
note that Foo and Bar are both simplified model of my real models to reduce the information amount of the given problem
Upvotes: 3
Views: 1895
Reputation: 711
While testing the models with the APIClient rest_framework.test.APIClient
and making a post request with more complex data and nested serializer, the format must be set explicitly to json
as such
def test_a_feature(self):
self.client = APIClient()
payload = {
"name": "A Foo",
"description": "A happy foo running in the woods of Central Park",
"bars": [
{name : "a"},
{name : "b"},
{name : "c"},
]
}
self.client.post(reverse('foo-list'), payload, format='json')
Upvotes: 3
Reputation: 302
I think I have found a great solution to your issue in the docs
Writable nested serializers
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved:
The docs continue to give an example of how you would implement this:
class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ['order', 'title', 'duration'] class AlbumSerializer(serializers.ModelSerializer): tracks = TrackSerializer(many=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] def create(self, validated_data): tracks_data = validated_data.pop('tracks') album = Album.objects.create(**validated_data) for track_data in tracks_data: Track.objects.create(album=album, **track_data) return album >>> data = { 'album_name': 'The Grey Album', 'artist': 'Danger Mouse', 'tracks': [ {'order': 1, 'title': 'Public Service Announcement', 'duration': 245}, {'order': 2, 'title': 'What More Can I Say', 'duration': 264}, {'order': 3, 'title': 'Encore', 'duration': 159}, ], } >>> serializer = AlbumSerializer(data=data) >>> serializer.is_valid() True >>> serializer.save() <Album: Album object>
So in your case, you would define the FooSerializer
and the BarsSerializer
like this:
class BarsSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ['name'] # Note that I have removed the 'foo' field since I have re-written your create method in the FooSerializer.
class FooSerializer(serializers.ModelSerializer):
bars = BarsSerializer(many=True)
class Meta:
model = Foo
fields = ['id', 'author', 'name', 'description', 'bars']
def create(self, validated_data):
validated_data['author'] = self.context['request'].user
bars_data = validated_data.pop('bars') # Validated data is your dict with the 'bars' list nested inside it
Foo = Foo.objects.create(**validated_data) # Foo object manager is tested and works
# Now we just iterate over the bars list...
for bar_data in bars_data:
#... and create a bar for each bar_data
Bar.objects.create(foo=foo, **bar_data)
return Foo
I haven't tested this code and it may not work, but it is taken right from the docs.
Upvotes: 0