djangodjango-rest-framework

Reverse Serializing Many to Many Relations Causes RecursionError


Following on from this answer, I've attempted to reverse serialize objects from a many to many relationship.

Using a serializer method field, I've tried to populate the people that have a person type however this is causing a RecursionError from django.

I can see the issue, PersonA has PersonTypeA who has PersonA etc. but I'm not sure how to 'flatten' the returned people to one level.

How can I overcome the recursion error?

Models:

class PersonType(models.Model):
    name = models.CharField(max_length=128)
    created = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now_add=True)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField(null=True)

class Person(models.Model):
    title = models.CharField(max_length=16)
    first_name = models.CharField(max_length=128)
    last_name = models.CharField(max_length=128)
    email_address = models.CharField(max_length=256)
    created = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now_add=True)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField(null=True)

    person_types = models.ManyToManyField(PersonType, related_name="people")

Serializers:

class PersonTypeSerializer(serializers.ModelSerializer):

    people = serializers.SerializerMethodField()

    class Meta:
        model = PersonType
        fields = '__all__'

    def get_people(self, obj):
        people = obj.people.all()
        response = PersonSerializer(people, many=True).data
        return response

class PersonSerializer(serializers.ModelSerializer):

    person_types = PersonTypeSerializer(many=True)

    class Meta:
        model = Person
        fields = '__all__'
        depth = 1

Solution

  • The RecursionError is happening not directly because of the models but rather due to the form on how you are trying to serialize your data.

    If a Person instance is included in a Type that contains that Person then you enter in a never ending recursion loop where your serializers are eternally calling each other.

    To solve the error immediately, instead of trying to serialize the QuerySet from your SerializerMethodField, return its values instead:

    class PersonTypeSerializer(serializers.ModelSerializer):
    
        people = serializers.SerializerMethodField()
    
        class Meta:
            model = PersonType
            fields = '__all__'
    
        def get_people(self, obj):
            people = obj.people.all()
            return people.values()
    
    class PersonSerializer(serializers.ModelSerializer):
        ...
    

    Also, I need to point out that you are trying to use depth to represent the nested relation. But instead, by creating your own serializer (PersonTypeSerializer), you are overriding it, so you could just have one serializer and let the framework traverse it for you:

    class PersonSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = Person
            fields = '__all__'
            depth = 1
    

    Although, that would not return people field which is part of your customization. Meaning that depth in this case is useless.