Reputation: 58848
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
Reputation: 88529
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
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
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