Reputation:
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
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
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
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
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