[Django]-Django RF ErrorDetail : This field is required with APIClient

3👍

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')

0👍

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.

👤Rayyan

Leave a comment