我正在尝试找出将带注释的字段(例如任何聚合的(计算的)字段)添加到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()
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer): class Meta: model = IceCreamCompany
所需的JSON输出:
[ { "name": "Pete's Ice Cream", "total_trucks": 20, "total_capacity": 4000 }, ... ]
我有几个可行的解决方案,但是每个解决方案都有一些问题。
选项1:为模型添加吸气剂并使用SerializerMethodFields
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']
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')
上面的代码也许可以重构一点,但是它不会改变以下事实:该选项将对每个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') )
这将在单个SQL查询中获得聚合的字段,但是我不确定如何将它们添加到序列化程序中,因为DRF不可思议地知道我已经在QuerySet中注释了这些字段。如果将total_trucks和total_capacity添加到序列化程序中,它将引发有关模型中不存在这些字段的错误。
通过使用View可以使选项2在不使用序列化器的情况下工作,但是如果模型包含很多字段,并且JSON中仅要求包含某些字段,那么在没有序列化器的情况下构建端点将是一个有点丑陋的技巧。
可能的解决方案:
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') )
class IceCreamCompanySerializer(serializers.ModelSerializer): total_trucks = serializers.IntegerField() total_capacity = serializers.IntegerField() class Meta: model = IceCreamCompany fields = ('name', 'total_trucks', 'total_capacity')