Django REST Framework 序列化程序中的嵌套注释字段

Posted

技术标签:

【中文标题】Django REST Framework 序列化程序中的嵌套注释字段【英文标题】:Nested annotate fields in Django REST Framework serializers 【发布时间】:2016-01-03 01:36:30 【问题描述】:

我正在尝试查看 Django REST Framework 序列化程序中的嵌套注释(聚合/计算)字段。这将允许更干净地处理带注释的字段。这篇文章与Aggregate (and other annotated) fields in Django Rest Framework serializers 类似,但是我想要一种类似的嵌套技术。在方法下方可以看到如何在没有嵌套的情况下工作,以及如何在嵌套情况下不工作。

我知道这可以手动实现(使用 Django 视图)或通过使用使我不感兴趣的数据库重载的方法来实现。但也许有一个高性能且优雅的解决方案来解决这个问题。

以下作品(未嵌套)

模型

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


class IceCreamTruck(models.Model):
    company = models.ForeignKey('IceCreamCompany', related_name='trucks')
    capacity = models.IntegerField()


class IceCreamTruckDriver(models.Model):
    name = models.CharField(max_length=255)
    first_name = models.CharField(max_length=255)
    truck = models.ForeignKey('IceCreamTruck', related_name='drivers')

序列化器

class IceCreamTruckDriverSerializer(serializers.ModelSerializer):

    class Meta:
        model = IceCreamTruckDriver
        fields = ('name', 'first_name')


class IceCreamTruckSerializer(serializers.ModelSerializer):
    drivers = IceCreamTruckDriverSerializer(many=True, read_only=True)

    class Meta:
        model = IceCreamTruck
        fields = ('capacity', 'drivers')


class IceCreamCompanySerializer(serializers.ModelSerializer):
    trucks = IceCreamTruckSerializer(many=True, read_only=True)
    amount_of_trucks = serializers.IntegerField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'trucks', 'amount_of_trucks')

视图集

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers')\
                           .annotate(amount_of_trucks=Count('trucks'))\
                           .all()

    serializer_class = IceCreamCompanySerializer

结果

"results": [
        
            "name": "Pete Ice Cream",
            "trucks": [
                
                    "capacity": 35,
                    "drivers": [
                        
                            "name": "Damian",
                            "first_name": "Ashley"
                        ,
                        
                            "name": "Wilfrid",
                            "first_name": "Lesley"
                        
                    ]
                ,
                
                    "capacity": 30,
                    "drivers": [
                        
                            "name": "Stevens",
                            "first_name": "Joseph"
                        
                    ]
                ,
                
                    "capacity": 30,
                    "drivers": []
                
            ],
            "amount_of_trucks": 3
        
    ]

以下不起作用(嵌套)

同款

序列化器

class IceCreamTruckDriverSerializer(serializers.ModelSerializer):

    class Meta:
        model = IceCreamTruckDriver
        fields = ('name', 'first_name')


class IceCreamTruckSerializer(serializers.ModelSerializer):
    drivers = IceCreamTruckDriverSerializer(many=True, read_only=True)
    amount_of_drivers = serializers.IntegerField()

    class Meta:
        model = IceCreamTruck
        fields = ('capacity', 'drivers', 'amount_of_drivers')


class IceCreamCompanySerializer(serializers.ModelSerializer):
    trucks = IceCreamTruckSerializer(many=True, read_only=True)

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

视图集

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers')\
                           .annotate(trucks__amount_of_drivers=Count('trucks__drivers'))\
                           .all()

    serializer_class = IceCreamCompanySerializer

结果

AttributeError at /ice/
Got AttributeError when attempting to get a value for field `amount_of_drivers` on serializer `IceCreamTruckSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `IceCreamTruck` instance.
Original exception text was: 'IceCreamTruck' object has no attribute 'amount_of_drivers'.

【问题讨论】:

您不应该将字段命名为 amount_of_drivers 而不是 trucks__amount_of_drivers 吗? 如果我在查询集中的注释函数中使用 amount_of_drivers,它会尝试在 IceCreamCompanySerializer 中查找 amount_of_drivers 字段,该字段不是嵌套的注释字段。我希望它在 IceCreamTruckSerializer 中成为可能。 不,先生,我只是建议重命名注释: .annotate(amount_of_drivers=Count('trucks__drivers'));错误命名了这个问题,在查询集中没有找到带注释的列的名称这一事实。注释列的名称只是一个名称,不会为您遍历关系。 您好,您最终能否在不使用SerializerMethodField 的情况下实现这一目标? @NicholasColes 是的,我仍然使用自定义视图,这不是最佳的。但是,您可以尝试自定义管理器,如 Campi 在另一个答案中所述。对于我的用例,这种方法会使其过于复杂。我也没有检查查询量。 【参考方案1】:

作为参考,还可以在模型 IceCreamTruck 上注释每辆卡车的司机数量,例如使用自定义管理器:

class AnnotatedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(amount_of_drivers=Count('drivers'))

class IceCreamTruck(models.Model):
    company = models.ForeignKey('IceCreamCompany', related_name='trucks')
    capacity = models.IntegerField()

    objects = AnnotatedManager()

那么您不需要注释视图集,因为amount_of_drivers 已经在trucks 上注释:

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers').all()
    serializer_class = IceCreamCompanySerializer 

它应该比在序列化器中计数更有效。

【讨论】:

好主意,“number 个驱动​​程序”也是如此。 ;-)【参考方案2】:

我使用 Django REST google 组在 IntegerField 中使用 read_only=True 得到了答案,这有助于消除错误,但该字段不再显示。也许我的注释是错误的。无论如何,我最终在 Django 中使用了自定义视图,因为我最终需要更多数据。但是您可以通过其他方式获取数据:

一个非常优雅的解决方案是删除注释函数并使用可以给我结果的 SerializerMethodField。

但是:这确实会引起很多查询!

同款

序列化器

class IceCreamTruckDriverSerializer(serializers.ModelSerializer):

    class Meta:
        model = IceCreamTruckDriver
        fields = ('name', 'first_name')


class IceCreamTruckSerializer(serializers.ModelSerializer):
    drivers = IceCreamTruckDriverSerializer(many=True, read_only=True)
    amount_of_drivers = serializers.SerializerMethodField()

    def get_amount_of_drivers(self, obj):
        return obj.drivers.count()

    class Meta:
        model = IceCreamTruck
        fields = ('capacity', 'drivers', 'amount_of_drivers')


class IceCreamCompanySerializer(serializers.ModelSerializer):
    trucks = IceCreamTruckSerializer(many=True, read_only=True)

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

视图集

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.prefetch_related('trucks', 'trucks__drivers').all()

    serializer_class = IceCreamCompanySerializer

结果

"results": [
        
            "name": "Pete Ice Cream",
            "trucks": [
                
                    "capacity": 35,
                    "drivers": [
                        
                            "name": "Damian",
                            "first_name": "Ashley"
                        ,
                        
                            "name": "Wilfrid",
                            "first_name": "Lesley"
                        
                    ],
                    "amount_of_drivers": 2
                ,
                
                    "capacity": 30,
                    "drivers": [
                        
                            "name": "Stevens",
                            "first_name": "Joseph"
                        
                    ],
                    "amount_of_drivers": 1
                ,
                
                    "capacity": 30,
                    "drivers": [],
                    "amount_of_drivers": 0
                
            ]
        
    ]

也可以像这样在模型中使用函数:Django Rest Framework Ordering on a SerializerMethodField(它在代码本身中可见)但我没有选择它,所以我不必过多地修改我的模型。这也会产生过多的查询。

【讨论】:

以上是关于Django REST Framework 序列化程序中的嵌套注释字段的主要内容,如果未能解决你的问题,请参考以下文章

python django-rest-framework 3.3.3 更新嵌套序列化程序

django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器

Django.rest_framework:如何序列化一对多?

Django 序列化器与 rest_framework 序列化器

Django-Rest-Framework 中的序列化程序问题

django.core 序列化器和 Django Rest Framework 序列化器之间的区别