Django Rest Framework-有效检索反向外键上的相关字段

时间:2018-10-31 22:02:47

标签: python django performance orm django-rest-framework

我有以下代表用户工作组的模型。每个工作组都有一个领导者和成员:

class WorkingGroup(models.Model):
    group_name = models.CharField(max_length=255)
    leader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)

class WorkingGroupMember(models.Model):
    group = models.ForeignKey(WorkingGroup, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

在DRF中,我想高效地检索所有组(有几百个)作为以下json对象的数组:

{
    'id': <the_group_id>
    'group_name': <the_group_name>
    'leader': <id_of_leader>
    'members': [<id_of_member_1>, <id_of_member_2>, ...]
}

为此,我设置了以下序列化器:

class WorkingGroupSerializer(serializers.ModelSerializer):
    members = serializers.SerializerMethodField()
    class Meta:
        model = WorkingGroup
        fields = ('id', 'group_name', 'leader', 'members',)

    def get_members(self, obj):
        return obj.workinggroupmember_set.all().values_list('user_id', flat=True)

在我看来,我可以做类似的事情:

groups = WorkingGroup.objects.all().prefetch_related('workinggroupmember_set')
group_serializer = WorkingGroupSerializer(groups, many=True)

这可行,并给出了预期的结果,但是我发现它根本无法很好地扩展,因为预取workinggroupmember_set似乎没有在get_members方法内部使用(Silky是显示一个查询以获取所有WorkingGroup个对象,然后显示一个workinggroupmember_set方法中每个get_members调用的查询)。是否可以在不使用members的情况下在序列化器中设置workinggroupmember_set字段以获取SerializerMethodField的扁平/单字段版本?还是其他可以让我正确使用预取的方式?

2 个答案:

答案 0 :(得分:2)

这里的问题是您在values_list之上执行all,这会使您的prefetch_related无效。当前无法使用values_list进行预取,请参见https://code.djangoproject.com/ticket/26565。您可以做的就是将其转换为python代码而不是SQL

class WorkingGroupSerializer(serializers.ModelSerializer):
    members = serializers.SerializerMethodField()
    class Meta:
        model = WorkingGroup
        fields = ('id', 'group_name', 'leader', 'members',)

    def get_members(self, obj):
        return [wgm.user_id for wgm in obj.workinggroupmember_set.all()]

答案 1 :(得分:0)

在最近使用DRF v3.9.1和django 2.1的项目中,我需要通过仅与父对象直接连接来递归地显示对象的所有子对象,而父对象可能有多个子对象。

之前,如果我要请求对象的“树”,我会得到:

{
    "uuid": "b85385c0e0a84785b6ca87ce50132659",
    "name": "a",
    "parent": null
}

通过应用下面显示的序列化,我得到:

{
    "uuid": "b85385c0e0a84785b6ca87ce50132659",
    "name": "a",
    "parent": null
    "children": [
        {
            "uuid": "efd26a820b4e4f7c8e56c812a7791fcb",
            "name": "aa",
            "parent": "b85385c0e0a84785b6ca87ce50132659"
            "children": [
                {
                    "uuid": "ca2441fc7abf49b6aa1f3ebbc2dae251",
                    "name": "aaa",
                    "parent": "efd26a820b4e4f7c8e56c812a7791fcb"
                    "children": [],
                }
            ],
        },
        {
            "uuid": "40e09c85775d4f1a8578bba9c812df0e",
            "name": "ab",
            "parent": "b85385c0e0a84785b6ca87ce50132659"
            "children": [],
        }
    ],
}

这是递归对象的models.py

class CategoryDefinition(BaseModelClass):
    name = models.CharField(max_length=100)
    parent = models.ForeignKey('self', related_name='children',
                               on_delete=models.CASCADE,
                               null=True, blank=True)

要获取外键中的所有反向对象,请将一个字段应用于序列化程序类:

class DeepCategorySerializer(serializers.ModelSerializer):
    children = serializers.SerializerMethodField()

    class Meta:
        model = models.CategoryDefinition
        fields = '__all__'

    def get_children(self, obj):
        return [DeepCategorySerializer().to_representation(cat) for cat in obj.children.all()]

然后将此序列化程序应用于DRF视图函数或泛型类,例如:

re_path(r'categories/(?P<pk>[\w\d]{32})/',
        generics.RetrieveUpdateDestroyAPIView.as_view(
            queryset=models.CategoryDefinition.objects.all(),
            serializer_class=serializers.DeepCategorySerializer),
        name='category-update'),