Aggregate (and other annotated) fields in Django Rest Framework serializers
Possible solution:
views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet): queryset = IceCreamCompany.objects.all() serializer_class = IceCreamCompanySerializer def get_queryset(self): return IceCreamCompany.objects.annotate( total_trucks=Count('trucks'), total_capacity=Sum('trucks__capacity') )
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer): total_trucks = serializers.IntegerField() total_capacity = serializers.IntegerField() class Meta: model = IceCreamCompany fields = ('name', 'total_trucks', 'total_capacity')
By using Serializer fields I got a small example to work. The fields must be declared as the serializer's class attributes so DRF won't throw an error about them not existing in the IceCreamCompany model.
I made a slight simplification of elnygreen's answer by annotating the queryset when I defined it. Then I don't need to override get_queryset()
.
# views.pyclass IceCreamCompanyViewSet(viewsets.ModelViewSet): queryset = IceCreamCompany.objects.annotate( total_trucks=Count('trucks'), total_capacity=Sum('trucks__capacity')) serializer_class = IceCreamCompanySerializer# serializers.pyclass IceCreamCompanySerializer(serializers.ModelSerializer): total_trucks = serializers.IntegerField() total_capacity = serializers.IntegerField() class Meta: model = IceCreamCompany fields = ('name', 'total_trucks', 'total_capacity')
As elnygreen said, the fields must be declared as the serializer's class attributes to avoid an error about them not existing in the IceCreamCompany model.
You can hack the ModelSerializer constructor to modify the queryset it's passed by a view or viewset.
class IceCreamCompanySerializer(serializers.ModelSerializer): total_trucks = serializers.IntegerField(readonly=True) total_capacity = serializers.IntegerField(readonly=True) class Meta: model = IceCreamCompany fields = ('name', 'total_trucks', 'total_capacity') def __new__(cls, *args, **kwargs): if args and isinstance(args[0], QuerySet): queryset = cls._build_queryset(args[0]) args = (queryset, ) + args[1:] return super().__new__(cls, *args, **kwargs) @classmethod def _build_queryset(cls, queryset): # modify the queryset here return queryset.annotate( total_trucks=..., total_capacity=..., )
There is no significance in the name _build_queryset
(it's not overriding anything), it just allows us to keep the bloat out of the constructor.