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.

Original source