5

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>"
        }
    ]
}
6
  • Did you read DRF docs on nested representations? This or this. Commented Nov 2, 2015 at 21:40
  • Hi @ivan, yes I've worked through it but I cannot get them to nest the way I'd like. I edited my question a bit with how it looks now. Commented Nov 2, 2015 at 21:46
  • 1
    The structure you want is no valid JSON Commented Nov 2, 2015 at 21:55
  • Sorry.. fixed it, should be valid now. Commented Nov 2, 2015 at 22:00
  • How does RecursiveField know which model field it serializes? Commented Nov 2, 2015 at 22:02

4 Answers 4

3

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')
Sign up to request clarification or add additional context in comments.

10 Comments

It was one of the solutions suggested in another question. It works with just the meta defined as you suggested, but I would like them nested under each other appropriately.
@Wilson - are you saying that the issue is that, with this solution, you are only seeing FKs instead of actual Section JSON objects?
I updated my question with the current output. Basically, I'm getting all the "section" objects that belong to a report in one level. However, I'd like section objects to be nested under eachother as they are subsections. Right now it is flat.
@Wilson - I've updated my answer. Give one of those methods a try and let me know how it goes.
Thanks! Looks like however, the section part in the SectionSerializer is just nesting itself. IE, section 1 has section 1 for the subsection. Updated question.
|
3

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,
        }
    }
]

Comments

2

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')

Comments

1

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__'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.