user2540748
user2540748

Reputation:

Django rest framework nested serializer with self referential objects

I've tried a few solutions posted elsewhere for this problem but with no luck. It seems like it is not natively supported in DRF. Does anyone have suggestions on how to accomplish this?

I have a reports model and a section model. A section is defined as follows:

class Section(models.Model):
    title = models.CharField(max_length=255)
    report = models.ForeignKey(Report)
    order = models.PositiveIntegerField()
    section = models.ForeignKey('self', related_name='section_section', blank=True, null=True)
    content = models.TextField(blank=True)

I want to have it display data like so under reports:

[
    {
        "id": 1,
        "title": "test",
        "subtitle": "test",
        "section_set": [
            {
                "id": 1,
                "title": "test",
                "report": 1,
                "order": 1,
                "section_set": [
                    {
                        "id": 1,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": null,
                        "content": "<p>test</p>"
                    },
                    {
                        "id": 2,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": 2,
                        "content": "<p>test</p>"
                    },
                    {
                        "id": 3,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": null,
                        "content": "<p>test</p>"
                    }
                ],
                "content": "<p>test</p>"
            },
            {
                "id": 2,
                "title": "test",
                "report": 1,
                "order": 1,
                "section": 2,
                "content": "<p>test</p>"
            },
            {
                "id": 3,
                "title": "test",
                "report": 1,
                "order": 1,
                "section": null,
                "content": "<p>test</p>"
            }
        ]
    }
]

My current (attempted) implementation looks like this:

class SubsectionSerializer(serializers.ModelSerializer):
class Meta:
    model = Section


class SectionSerializer(serializers.ModelSerializer):
    section = SubsectionSerializer()

    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'section', 'content')


class CountryReportSerializer(serializers.ModelSerializer):
    section_set = SectionSerializer(many=True)

    class Meta:
        model = CountryReport
        fields = ('id', 'title', 'subtitle', 'section_set')


class MapsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Map
        fields = ('id', 'country', 'map_image', 'report')

but the output looks like this:

{
    "id": 1,
    "title": "test",
    "subtitle": "test",
    "section_set": [
        {
            "id": 1,
            "title": "Section 1",
            "report": 1,
            "order": 1,
            "section": null,
            "content": "<p>test</p>"
        },
        {
            "id": 2,
            "title": "Section 2",
            "report": 1,
            "order": 1,
            "section": null,
            "content": "<p>test</p>"
        },
        {
            "id": 3,
            "title": "Subsection 1",
            "report": 1,
            "order": 1,
            "section": {
                "id": 1,
                "title": "Section 1",
                "order": 1,
                "content": "<p>test</p>",
                "report": 1,
                "section": null
            },
            "content": "<p>test</p>"
        }
    ]
}

Upvotes: 5

Views: 8837

Answers (4)

Ankit Kumar Singh
Ankit Kumar Singh

Reputation: 71

For anyone wanting to create a nested tree like structure, here is what worked for me with DRF 3.12.1 :

# Model class
class Foo:
    foo = models.ForeignKey('Foo', null=True, blank=True)

# Serializer class
class FooSerializer(serializers.ModelSerializer):
    foo = serializers.SerializerMethodField()

    def get_foo(self, obj):
        return FooSerializer(obj.foo).data if obj.foo else None

    class Meta:
        model = Foo
        fields = '__all__'

This will give a response like:

[
    {
        "id": 105,
        "a": {
            "id": 106,
            "a": null,
        }
    }
]

Upvotes: 3

S&#233;rgio
S&#233;rgio

Reputation: 7249

KISS method , RecursiveField serialize just return values

class RecursiveField(serializers.ModelSerializer): 
    def to_representation(self, value):
        serializer_data = TypeSerializer(value, context=context).data
        return serializer_data
    class Meta:
            model = Type
            fields = '__all__'

class TypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)

    class Meta:
        model = Type
        fields = '__all__'

in model.py

class Type(models.Model):
    id = models.AutoField(primary_key=True)
    extends_type = models.ForeignKey('self', models.SET_NULL, null=True)

you can enter in loop easily, to avoid that , we can copy TypeSerializer to SubTypeSerializer and count or control deep with on key counter in context under

def to_representation(self, value):
    lvalue = context.get('count', 0)
    lvalue += 1 
    context.update({'count': lvalue})
    serializer_data = SubTypeSerializer(value, context=context).data
    return serializer_data

class TypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)
    def to_representation(self, instance):
        self.context.update({'count': 0})
        return super().to_representation(instance)

    class Meta:
        model = Type
        fields = '__all__'

class SubTypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)

    class Meta:
        model = Type
        fields = '__all__'

Upvotes: 1

Adam_O
Adam_O

Reputation: 341

The way you are defining subsection doesn't link it to your section field, just like the error suggests. Have you tried defining your serializer simply like this:

class SectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section

Because Section has a FK to section, it should be returned as you would expect from the serializer.

To ensure that the JSON results returned by this serializer contain nested JSON objects, instead of only FKs, there are two routes you can take:

1), depth=

class SectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section
        depth=2

This will follow FKs down, building JSON objects as it goes to the depth that you specify.

2) Define a SubSerializer to handle the JSON object creation:

class SubsectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section

class SectionSerializer(serializers.ModelSerializer):
    section = serializers.SubsectionSerializer()
    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'section', 'content')

------------------------EDIT---------------------------

For clarity, it might make sense to rename the section related bits of your model:

class Section(models.Model):
    title = models.CharField(max_length=255)
    report = models.ForeignKey(Report)
    order = models.PositiveIntegerField()
    parent_section = models.ForeignKey('self', related_name='child_sections', blank=True, null=True)
    content = models.TextField(blank=True)

With the new names, you should be able to use the following serializer:

class SectionSerializer(serializers.ModelSerializer):
    child_sections = serializers.SubsectionSerializer(many=True)
    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'child_sections', 'content')

Upvotes: 3

user2540748
user2540748

Reputation:

Got it working with the following solution:

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data


class SectionSerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True)

    class Meta:
        model = Section
        fields = ('id', 'order', 'title', 'content', 'parent', 'children')


class CountryReportSerializer(serializers.ModelSerializer):
    section_set = serializers.SerializerMethodField('get_parent_sections')

    @staticmethod
    def get_parent_sections(self, obj):
        parent_sections = Section.objects.get(parent=None, pk=obj.pk)
        serializer = SectionSerializer(parent_sections)
        return serializer.data

    class Meta:
        model = CountryReport
        fields = ('id', 'title', 'subtitle', 'section_set')


class MapsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Map
        fields = ('id', 'country', 'map_image', 'report')

Upvotes: 2

Related Questions