Aggregate (and other annotated) fields in Django Rest Framework serializers

后端 未结 3 1937
不思量自难忘°
不思量自难忘° 2020-11-29 18:20

I am trying to figure out the best way to add annotated fields, such as any aggregated (calculated) fields to DRF (Model)Serializers. My use case is simply a situation where

相关标签:
3条回答
  • 2020-11-29 18:31

    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.py
    class IceCreamCompanyViewSet(viewsets.ModelViewSet):
        queryset = IceCreamCompany.objects.annotate(
                total_trucks=Count('trucks'),
                total_capacity=Sum('trucks__capacity'))
        serializer_class = IceCreamCompanySerializer
    
    # serializers.py
    class 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.

    0 讨论(0)
  • 2020-11-29 18:36

    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.

    0 讨论(0)
  • 2020-11-29 18:39

    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.

    0 讨论(0)
提交回复
热议问题