Django RestFramework 序列化器中的聚合(和其他带注释的)字段

我正在尝试找出添加带注释字段的最佳方法,比如向 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()

序列化器 py

class IceCreamCompanySerializer(serializers.ModelSerializer):
class Meta:
model = IceCreamCompany

期望的 JSON 输出:

[


{
"name": "Pete's Ice Cream",
"total_trucks": 20,
"total_capacity": 4000
},
...
]

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

选项1: 向模型添加 getters 并使用 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']

序列化器 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')

上面的代码也许可以进行一些重构,但是它不会改变这样一个事实,即这个选项将执行2个额外的 SQL 查询 每间冰淇淋公司,这不是很有效。

选项2: ViewSet.get _ queryset 中的注释

最初描述的 model. 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')
)

这将获得单个 SQL 查询中的聚合字段,但我不确定如何将它们添加到 Serializer 中,因为 DRF 不知道我已经在 QuerySet 中注释了这些字段。如果我向序列化程序添加 total _ ó 和 total _ ability,它将抛出一个关于 Model 上不存在这些字段的错误。

选项2可以通过使用 观景在没有序列化器的情况下工作,但是如果模型包含很多字段,并且只有一些字段需要在 JSON 中,那么在没有序列化器的情况下构建端点将是一种有点丑陋的做法。

30704 次浏览

可能的解决办法:

视野,视野

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')
)

序列化器 py

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


class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')

通过使用 序列化器字段我得到了一个工作的小例子。这些字段必须声明为序列化程序的类属性,这样 DRF 就不会因为它们不存在于 IceCreamCompany 模型中而抛出错误。

您可以修改 ModelSerializer 构造函数来修改它通过视图或视图传递的查询集。

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=...,
)

_build_queryset这个名字没有任何意义(它没有覆盖任何内容) ,它只是允许我们避免构造函数的膨胀。

通过在定义查询集时对其进行注释,我对 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')

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