Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
259 views
in Technique[技术] by (71.8m points)

python - DRF: Simple foreign key assignment with nested serializers?

With Django REST Framework, a standard ModelSerializer will allow ForeignKey model relationships to be assigned or changed by POSTing an ID as an Integer.

What's the simplest way to get this behavior out of a nested serializer?

Note, I am only talking about assigning existing database objects, not nested creation.

I have hacked away around this in the past with additional 'id' fields in the serializer and with custom create and update methods, but this is such a seemingly simple and frequent issue for me that I'm curious to know the best way.

class Child(models.Model):
    name = CharField(max_length=20)

class Parent(models.Model):
    name = CharField(max_length=20)
    phone_number = models.ForeignKey(PhoneNumber)
    child = models.ForeignKey(Child)

class ChildSerializer(ModelSerializer):
    class Meta:
        model = Child

class ParentSerializer(ModelSerializer):
    # phone_number relation is automatic and will accept ID integers
    children = ChildSerializer() # this one will not

    class Meta:
        model = Parent
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...