l0b0
l0b0

Reputation: 58848

How to include a parent object when serializing a Django Model?

I have two simple models with a foreign key relation like this:

class Foo(models.Model):
    code = models.CharField(max_length=255)


class Bar(models.Model):
    foo = models.ForeignKey(Foo)
    description = models.CharField(max_length=255)

The current rest_framework-based serializers look like this:

class FooSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Foo
        fields = ('id', 'code')


class BarSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Bar
        fields = ('id', 'foo', 'description')

So a GET request for a Bar will return something like this:

{
    "id": 1,
    "foo": 2,
    "description": "[…]"
}

How do I change BarSerializer to instead return the full Foo object, like this:

{
    "id": 1,
    "foo": {
        "id": 2,
        "code": "[…]"
    },
    "description": "[…]"
}

?

Keep in mind I still need to be able to create a Bar by providing only a description and Foo ID. I've tried various things including specifying foo = FooSerializer() in BarSerializer. The problem is that when I want to create a new Bar and link it to an existing Foo as before, it complains that I've not provided Foo's code property.

Upvotes: 2

Views: 446

Answers (2)

JPG
JPG

Reputation: 88529

Simple and Elegant solution

override to_represention() method as,

class BarSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Bar
        fields = ('id', 'foo', 'description')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['foo'] = FooSerializer(instance.foo).data
        return data




Orginal version

use depth=1 in BarSerializer serializer


class BarSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Bar
        fields = ('id', 'foo', 'description')
        depth = 1


Reference
1. depth [DRF-Doc]

Update-1
Use Two different Serializers for Read and Write operations.


class BarWriteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Bar
        fields = ('id', 'foo', 'description')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['foo'] = FooSerializer(instance.foo).data
        return data


class BarReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Bar
        fields = ('id', 'foo', 'description')
        depth = 1

and in your views, override the get_serializer_class() method as,

from rest_framework import viewsets


class SampleViewset(viewsets.ModelViewSet):
    queryset = Bar.objects.all()

    def get_serializer_class(self):
        if self.action == 'create':
            return BarWriteSerializer
        return BarReadSerializer

The payload to use while Bar creation,

{
    "foo":1,# The "pk" of "Foo" instance
    "description":"bar description"
}

Upvotes: 3

Sachin
Sachin

Reputation: 3674

Solution:

class FooSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Foo
        fields = ('id', 'code')


class BarSerializer(serializers.ModelSerializer):
    foo = FooSerializer(models.Foo.objects.all(), read_only=True)
    foo_id = serializers.PrimaryKeyRelatedField(
        source='foo',
        queryset=models.Foo.objects.all(),
        write_only=True
    )

    class Meta:
        model = models.Bar
        fields = ('id', 'foo', 'foo_id', 'description')

Use a nested serializer as read_only to get the full Foo object.
Use a write_only field foo_id to use it for create/update.

Now, your request data will look like:

{ 'foo_id': 1, 'description': 'foo bar' }

Alternatively, if you dont want two fields, one for read and another for write, you can override the create/update methods of the serializer to capture the foo's id.

Example:

class BarSerializer(serializers.ModelSerializer):
    foo = FooSerializer(Foo.objects.all())

    class Meta:
        model = models.Bar
        fields = ('id', 'foo', 'description')

    def create(self, validated_data):
        foo = validated_data.pop('foo')
        bar = Bar.objects.create(
            foo=foo.id,
            **validated_data
        )
        return bar

    def update(self, instance, validated_data):
        foo = validated_data.pop('foo')
        instance.foo = foo
        instance.update(
            **validated_data
        )
        return instance

The request data, in this case, will be:

{ 'foo': {'id': '1', 'code': 'AAA'}, 'description': 'foo bar' }

Upvotes: 0

Related Questions