Django Rest Framework Recursive Nested Parent Serialization
Question:
I have a model with a self referential field called parent.
Model:
class Zone(BaseModel):
name = models.CharField(max_length=200)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='children')
def __unicode__(self):
return self.name
Serializer:
class ZoneSerializer(ModelSerializer):
parent = PrimaryKeyRelatedField(many=False, queryset=Zone.objects.all())
parent_disp = StringRelatedField(many=False, source="parent")
class Meta:
model = Zone
fields = ('id', 'name', 'parent', 'parent_disp')
Now I want to serialize the parent of the zone and its parent and its parent till parent is none.
I found recursive serialization methods for children but not for parent.
How can I do this?
Answers:
Try use SerializerMethodField here:
def get_parent(self, obj):
# query what your want here.
I’m not sure D-R-F has build-in methods for this, but you can use query to get what you want in this method.
Ok, I got it working like that.
class ZoneSerializer(ModelSerializer):
parent = SerializerMethodField()
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def get_parent(self, obj):
if obj.parent is not None:
return ZoneSerializer(obj.parent).data
else:
return None
You also can do:
class ZoneSerializer(ModelSerializer):
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def to_representation(self, instance):
self.fields['parent'] = ZoneSerializer(read_only=True)
return super(ZoneSerializer, self).to_representation(instance)
Just wanted to add an additional solution that worked better for me.
The answer above using the SerializerMethodField
and then instantiating a new serializer class works nicely, but if you are trying to serialize a larger nested dataset instantiating a new serializer for each of your nested objects might become quite slow.
Trying to use a single instance of the serializer for all the nested objects made it a lot faster.
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return self.to_representation(obj)
else:
return None
One more step could be to also convert your return of the serializer method field to a ReturnDict
instance, which is what a serializer normally does when you call serializer.data
property.
from rest_framework.utils.serializer_helpers import ReturnDict
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return ReturnDict(self.to_representation(obj), serializer=self)
else:
return None
There are some potential disadvantages of this approach of course, like loosing validation that happens when instantiating a serializer, harder to modify for nested objects, etc.
If you are confident in your serializer setup and data being passed to the serializer this can be a nice optimization.
I have a model with a self referential field called parent.
Model:
class Zone(BaseModel):
name = models.CharField(max_length=200)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='children')
def __unicode__(self):
return self.name
Serializer:
class ZoneSerializer(ModelSerializer):
parent = PrimaryKeyRelatedField(many=False, queryset=Zone.objects.all())
parent_disp = StringRelatedField(many=False, source="parent")
class Meta:
model = Zone
fields = ('id', 'name', 'parent', 'parent_disp')
Now I want to serialize the parent of the zone and its parent and its parent till parent is none.
I found recursive serialization methods for children but not for parent.
How can I do this?
Try use SerializerMethodField here:
def get_parent(self, obj):
# query what your want here.
I’m not sure D-R-F has build-in methods for this, but you can use query to get what you want in this method.
Ok, I got it working like that.
class ZoneSerializer(ModelSerializer):
parent = SerializerMethodField()
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def get_parent(self, obj):
if obj.parent is not None:
return ZoneSerializer(obj.parent).data
else:
return None
You also can do:
class ZoneSerializer(ModelSerializer):
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def to_representation(self, instance):
self.fields['parent'] = ZoneSerializer(read_only=True)
return super(ZoneSerializer, self).to_representation(instance)
Just wanted to add an additional solution that worked better for me.
The answer above using the SerializerMethodField
and then instantiating a new serializer class works nicely, but if you are trying to serialize a larger nested dataset instantiating a new serializer for each of your nested objects might become quite slow.
Trying to use a single instance of the serializer for all the nested objects made it a lot faster.
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return self.to_representation(obj)
else:
return None
One more step could be to also convert your return of the serializer method field to a ReturnDict
instance, which is what a serializer normally does when you call serializer.data
property.
from rest_framework.utils.serializer_helpers import ReturnDict
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return ReturnDict(self.to_representation(obj), serializer=self)
else:
return None
There are some potential disadvantages of this approach of course, like loosing validation that happens when instantiating a serializer, harder to modify for nested objects, etc.
If you are confident in your serializer setup and data being passed to the serializer this can be a nice optimization.