DRF: Simple foreign key assignment with nested serializers?

Updated on July 05 2020

This post is getting more attention and it indicates more people have a similar situation. So I decided to add a generic way to handle this problem. This generic way is best suitable for you if you have more serializers that need to change to this format

Since DRF doesn’t provide this functionality out of the box, we need to create a serializer field first.

from rest_framework import serializers


class RelatedFieldAlternative(serializers.PrimaryKeyRelatedField):
    def __init__(self, **kwargs):
        self.serializer = kwargs.pop('serializer', None)
        if self.serializer is not None and not issubclass(self.serializer, serializers.Serializer):
            raise TypeError('"serializer" is not a valid serializer class')

        super().__init__(**kwargs)

    def use_pk_only_optimization(self):
        return False if self.serializer else True

    def to_representation(self, instance):
        if self.serializer:
            return self.serializer(instance, context=self.context).data
        return super().to_representation(instance)

I am not well impressed with this class name, RelatedFieldAlternative, you can use anything you want.
Then use this new serializer field in your parent serializer as,

class ParentSerializer(ModelSerializer):
   child = RelatedFieldAlternative(queryset=Child.objects.all(), serializer=ChildSerializer)

    class Meta:
        model = Parent
        fields="__all__"

Original Post

Using two different fields would be ok (as @Kevin Brown and @joslarson mentioned), but I think it’s not perfect (to me). Because getting data from one key (child) and sending data to another key (child_id) might be a little bit ambiguous for front-end developers. (no offense at all)

So, what I suggest here is, override the to_representation() method of ParentSerializer will do the job.

def to_representation(self, instance):
    response = super().to_representation(instance)
    response['child'] = ChildSerializer(instance.child).data
    return response


Complete representation of Serializer

class ChildSerializer(ModelSerializer):
    class Meta:
        model = Child
        fields="__all__"


class ParentSerializer(ModelSerializer):
    class Meta:
        model = Parent
        fields="__all__"

    def to_representation(self, instance):
        response = super().to_representation(instance)
        response['child'] = ChildSerializer(instance.child).data
        return response

Advantage of this method?

By using this method, we don’t need two separate fields for creation and reading. Here both creation and reading can be done by using child key.

Sample payload to create parent instance

{
        "name": "TestPOSTMAN_name",
        "phone_number": 1,
        "child": 1
    }

Screenshot
POSTMAN screenshot

Leave a Comment