Django Rest Framework序列化程序中的聚合(和其他带注释的)字段

eln*_*ren 40 django python-3.x django-rest-framework

我试图找出添加带注释字段的最佳方法,例如任何聚合(计算)字段到DRF(模型)序列化程序.我的用例只是一种情况,即端点返回未存储在数据库中但是从数据库计算的字段.

我们来看下面的例子:

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key = True, max_length = 255)

class IceCreamTruck(models.Model):
    company = models.ForeignKey('IceCreamCompany', related_name='trucks')
    capacity = models.IntegerField()
Run Code Online (Sandbox Code Playgroud)

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    class Meta:
        model = IceCreamCompany
Run Code Online (Sandbox Code Playgroud)

所需的JSON输出:

[

    {
        "name": "Pete's Ice Cream",
        "total_trucks": 20,
        "total_capacity": 4000
    },
    ...
]
Run Code Online (Sandbox Code Playgroud)

我有几个解决方案可行,但每个都有一些问题.

选项1:将getter添加到模型并使用SerializerMethodFields

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key=True, max_length=255)

    def get_total_trucks(self):
        return self.trucks.count()

    def get_total_capacity(self):
        return self.trucks.aggregate(Sum('capacity'))['capacity__sum']
Run Code Online (Sandbox Code Playgroud)

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):

    def get_total_trucks(self, obj):
        return obj.get_total_trucks

    def get_total_capacity(self, obj):
        return obj.get_total_capacity

    total_trucks = SerializerMethodField()
    total_capacity = SerializerMethodField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')
Run Code Online (Sandbox Code Playgroud)

上面的代码也许可以稍微重构一下,但它不会改变这个选项每个IceCreamCompany执行2个额外SQL查询的事实,这个查询效率不高.

选项2:在ViewSet.get_queryset中进行批注

models.py如最初描述的那样.

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')
        )
Run Code Online (Sandbox Code Playgroud)

这将在单个SQL查询中获取聚合字段,但我不确定如何将它们添加到Serializer中,因为DRF不会神奇地知道我在QuerySet中注释了这些字段.如果我将total_trucks和total_capacity添加到序列化程序,它将抛出有关模型上不存在的这些字段的错误.

通过使用View,可以使选项2在没有序列化程序的情况下工作,但如果模型包含许多字段,并且只需要一些字段在JSON中,那么在没有序列化程序的情况下构建端点将是一个有点难看的黑客.

eln*_*ren 50

可能的方法:

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')
        )
Run Code Online (Sandbox Code Playgroud)

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    total_trucks = serializers.IntegerField()
    total_capacity = serializers.IntegerField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')
Run Code Online (Sandbox Code Playgroud)

通过使用Serializer字段,我得到了一个小例子.这些字段必须声明为序列化程序的类属性,因此DRF不会抛出IceCreamCompany模型中不存在的错误.

  • 这是推荐的解决方案。DRF 无法内省这些字段,因此您必须手动指定它们。 (4认同)
  • 我希望有一个解决方案,我可以在 SQL 实际触发之前修改序列化程序中的查询集。在序列化程序生命周期中我可以插入注释钩子没有意义吗?能够序列化基于 `IceCreamCompany` 模型构建的任何查询集,而无需手动注释...... (2认同)

Don*_*kby 9

我通过在定义查询集时对其进行注释,对elnygreen 的答案进行了轻微的简化。然后我不需要覆盖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')
Run Code Online (Sandbox Code Playgroud)

正如 elnygreen 所说,这些字段必须声明为序列化程序的类属性,以避免出现关于它们不存在于 IceCreamCompany 模型中的错误。