I currently have this Django models I want to serialize:
class Result(models.Model):
...
routes = models.ManyToManyField(Route)
...
class Route(models.Model):
...
class Feature(models.Model):
result = models.ForeignKey(Result)
route = models.ForeignKey(Route)
description = models.TextField()
And the DRF serializers looks like:
class ResultSerializer(serializers.ModelSerializer):
...
route = RouteSerializer(many=True, required=False)
...
class Meta:
model = Result
fields = '__all__'
class FeatureField(serializers.CharField):
"""
Accepts text in the writes and looks up the correct feature for the reads.
"""
def get_attribute(self, obj):
# We pass the object instance onto `to_representation`, not just the field attribute.
return obj
def to_representation(self, obj):
try:
search_result = self.root.child.instance
# FIXME: this is the problem.
feature = Feature.objects.get(route=obj.id, search_result=search_result)
feature = feature.description
except Feature.DoesNotExist:
feature = None
return feature
class RouteSerializer(serializers.ModelSerializer):
description = FeatureField(required=False)
class Meta:
model = Route
fields = '__all__'
The problem I mean in the code is that this works when I'm using a ResultSerializer with just one instance, but if I want to serialize several instances in a list view for example, and I pass a queryset to the serializer, DRF applies a ListSerializer on top of it and now the self.root.instance is a list of the records, and I can't access the individual Results that call the nested RouteSerializer so I can't retrieve the correct Feature.
I jumped into DRF code and finally understood what was going on:
If you serialize just one instance with serializer = ResultSerializer(result), the serializer.instance contains only this single, particular result instance, and the nested serializers and fields can access it without problem using self.root.instance.
Now, if you serialize several instances, like the default list action does, what really happens is the following:
serializer = ResultSerializer(queryset, many=True) is performedmany=True in the arguments triggers the many_init() method from BaseSerializer, and this creates a single ResultSerializer with the queryset as instance, so serializer.instance is the queryset.ListSerializer extending ResultSerializer and its instance again is the queryset.What I got wrong is thinking that the ListSerializer would create separated ResultSerializers for each element in the queryset.
How I finally solved this is overriding the ResultSerializer.to_representation() method:
class ResultSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
# When we call Results with many=True, the serializer.instance is a list with several records,
# we can't know which particular instance is spawning the nested serializers so we add it here.
self._instance = instance
return super(ResultSerializer, self).to_representation(instance)
and finally consume it in the FeatureField like this:
class FeatureField(serializers.CharField):
"""
Accepts text in the writes and looks up the correct feature for the reads.
"""
def get_attribute(self, obj):
# We pass the object instance onto `to_representation`, not just the field attribute.
return obj
def to_representation(self, obj):
# If the root is a ListSerializer, retrieve the right Result instance using the `_instance` attribute.
try:
if isinstance(self.root, serializers.ListSerializer):
search_result = self.root.child._instance
else:
search_result = self.root.instance
feature = Feature.objects.get(route=obj.id, search_result=search_result)
feature = feature.pickup_instructions
except Feature.DoesNotExist:
feature = None
return feature
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With