Accessing particular instances in a DRF ListSerializer
django, django-rest-framework, python
Solution
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:
- a call like `serializer = ResultSerializer(queryset, many=True)` is performed
- having `many=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.
- next it creates a single `ListSerializer` extending `ResultSerializer` and its instance again is the queryset.
What I got wrong is thinking that the `ListSerializer` would create separated `ResultSerializer`s 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
Problem
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.