I am trying to view nested annotate (aggregated/calculated) fields in Django REST Framework serializers. This would allow to work more cleanly with annotated fields. This post is similar to Aggregate (and other annotated) fields in Django Rest Framework serializers however I would like a similar technique to work nested. Below the methodology is visible on how this works without nesting and how it doesn't seem to work with nesting.
I know this could be achieved manually (with a Django View) or by using methods that overload the database which I am not interested in. But maybe there is a performant and elegant solution for this problem.
The following works (not nested)
Models
class IceCreamCompany(models.Model):
name = models.CharField(max_length=255)
class IceCreamTruck(models.Model):
company = models.ForeignKey('IceCreamCompany', related_name='trucks')
capacity = models.IntegerField()
class IceCreamTruckDriver(models.Model):
name = models.CharField(max_length=255)
first_name = models.CharField(max_length=255)
truck = models.ForeignKey('IceCreamTruck', related_name='drivers')
Serializers
class IceCreamTruckDriverSerializer(serializers.ModelSerializer):
class Meta:
model = IceCreamTruckDriver
fields = ('name', 'first_name')
class IceCreamTruckSerializer(serializers.ModelSerializer):
drivers = IceCreamTruckDriverSerializer(many=True, read_only=True)
class Meta:
model = IceCreamTruck
fields = ('capacity', 'drivers')
class IceCreamCompanySerializer(serializers.ModelSerializer):
trucks = IceCreamTruckSerializer(many=True, read_only=True)
amount_of_trucks = serializers.IntegerField()
class Meta:
model = IceCreamCompany
fields = ('name', 'trucks', 'amount_of_trucks')
Viewset
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers')\
.annotate(amount_of_trucks=Count('trucks'))\
.all()
serializer_class = IceCreamCompanySerializer
Result
"results": [
{
"name": "Pete Ice Cream",
"trucks": [
{
"capacity": 35,
"drivers": [
{
"name": "Damian",
"first_name": "Ashley"
},
{
"name": "Wilfrid",
"first_name": "Lesley"
}
]
},
{
"capacity": 30,
"drivers": [
{
"name": "Stevens",
"first_name": "Joseph"
}
]
},
{
"capacity": 30,
"drivers": []
}
],
"amount_of_trucks": 3
}
]
The following does not work (nested)
Same models
Serializers
class IceCreamTruckDriverSerializer(serializers.ModelSerializer):
class Meta:
model = IceCreamTruckDriver
fields = ('name', 'first_name')
class IceCreamTruckSerializer(serializers.ModelSerializer):
drivers = IceCreamTruckDriverSerializer(many=True, read_only=True)
amount_of_drivers = serializers.IntegerField()
class Meta:
model = IceCreamTruck
fields = ('capacity', 'drivers', 'amount_of_drivers')
class IceCreamCompanySerializer(serializers.ModelSerializer):
trucks = IceCreamTruckSerializer(many=True, read_only=True)
class Meta:
model = IceCreamCompany
fields = ('name', 'trucks')
Viewset
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers')\
.annotate(trucks__amount_of_drivers=Count('trucks__drivers'))\
.all()
serializer_class = IceCreamCompanySerializer
Result
AttributeError at /ice/
Got AttributeError when attempting to get a value for field `amount_of_drivers` on serializer `IceCreamTruckSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `IceCreamTruck` instance.
Original exception text was: 'IceCreamTruck' object has no attribute 'amount_of_drivers'.
For reference, it is also possible to annotate the amount of drivers per Truck on the model
IceCreamTruck
, for example with a custom manager:Then you don't need to annotate the viewset because
amount_of_drivers
is already annotated ontrucks
:It should be more efficient than counting inside the serializer.
I got an answer using the Django REST google groups to use read_only=True inside the IntegerField, which helped removing the error but then the field wasn't displayed anymore. Maybe my annotation was wrong. Anyway I ended up using a custom view in Django since I ended up needing more data. However you can get the data in other ways:
A very elegant solution would be to remove the annotate function and use a SerializerMethodField which can give me my result.
HOWEVER: this does make a lot of queries!!
Same models
Serializers
Viewset
Result
It's also possible to use functions inside the models like this: Django Rest Framework Ordering on a SerializerMethodField (it's visible in the code itself) but I didn't choose it so I don't have to modify my models too much. This also makes too many queries.