Aggregate (and other annotated) fields in Django Rest Framework serializers Aggregate (and other annotated) fields in Django Rest Framework serializers python-3.x python-3.x

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.