python - django rest framework - 向后序列化以避免 prefetch_related

标签 python django django-models django-rest-framework django-related-manager

我有两个模型,ItemItemGroup:

class ItemGroup(models.Model):
   group_name = models.CharField(max_length=50)
   # fields..

class Item(models.Model):
   item_name = models.CharField(max_length=50)
   item_group = models.ForeignKey(ItemGroup, on_delete=models.CASCADE)
   # other fields..

我想编写一个序列化程序,它将获取所有项目组及其项目列表作为嵌套数组。

所以我想要这个输出:

[ {group_name: "item group name", "items": [... list of items ..] }, ... ]

如我所见,我应该使用 django rest 框架来编写:

class ItemGroupSerializer(serializers.ModelSerializer):
   class Meta:
      model = ItemGroup
      fields = ('item_set', 'group_name') 

意味着,我必须为 ItemGroup(不是为 Item)编写一个序列化程序。 为了避免很多查询,我传递了这个查询集:

ItemGroup.objects.filter(**filters).prefetch_related('item_set')

我看到的问题是,对于大型数据集,prefetch_related 会导致带有非常大的 sql IN 子句的额外查询,我可以通过查询避免这种情况在 Item 对象上:

Item.objects.filter(**filters).select_related('item_group')

这会导致更好的 JOIN。

是否可以查询 Item 而不是 ItemGroup,并且还具有相同的序列化输出?

最佳答案

使用 prefetch_related 您将有两个查询 + 大 IN 子句问题,尽管它已被证明并且可移植。

我会根据您的字段名称给出一个更多示例的解决方案。它将创建一个函数,该函数使用您的 select_related querysetItem 的序列化程序进行转换。它将覆盖 View 的列表函数,并将一个序列化程序数据转换为另一个序列化程序数据,从而为您提供所需的表示形式。它将仅使用一个查询并解析结果将在 O(n) 中,因此它应该很快。

您可能需要重构 get_data 以便向结果中添加更多字段。

class ItemSerializer(serializers.ModelSerializer):
    group_name = serializers.CharField(source='item_group.group_name')

    class Meta:
        model = Item
        fields = ('item_name', 'group_name')

class ItemGSerializer(serializers.Serializer):
    group_name = serializers.CharField(max_length=50)
    items = serializers.ListField(child=serializers.CharField(max_length=50))

在 View 中:

class ItemGroupViewSet(viewsets.ModelViewSet):
    model = models.Item
    serializer_class = serializers.ItemSerializer
    queryset = models.Item.objects.select_related('item_group').all()

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            data = self.get_data(serializer.data)
            s = serializers.ItemGSerializer(data, many=True)
            return self.get_paginated_response(s.data)

        serializer = self.get_serializer(queryset, many=True)
        data = self.get_data(serializer.data)
        s = serializers.ItemGSerializer(data, many=True)
        return Response(s.data)

    @staticmethod
    def get_data(data):
        result, current_group = [], None
        for elem in data:
            if current_group is None:
                current_group = {'group_name': elem['group_name'], 'items': [elem['item_name']]}
            else:
                if elem['group_name'] == current_group['group_name']:
                    current_group['items'].append(elem['item_name'])
                else:
                    result.append(current_group)
                    current_group = {'group_name': elem['group_name'], 'items': [elem['item_name']]}

        if current_group is not None:
            result.append(current_group)
        return result

这是我用假数据得到的结果:

[{
    "group_name": "group #2",
    "items": [
        "first item",
        "2 item",
        "3 item"
    ]
},
{
    "group_name": "group #1",
    "items": [
        "g1 #1",
        "g1 #2",
        "g1 #3"
    ]
}]

关于python - django rest framework - 向后序列化以避免 prefetch_related,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53874174/

相关文章:

python - 将数组添加到 Pandas 数据框中

javascript - Django - 使用 Javascript 将 ID 传递到 url 模板标记中

python - 如何在 Windows 10 中安装 Django

python - 如何将 timedelta 与 timezone.now 一起使用为默认值?

mysql - Django 中的模型表被删除

python - Django 模板中有两种不同的形式

python - 根据列表中的多个单词从 pandas 数据框中提取所有短语

python - 使用 django-reversion 获取所有修订

python - Django 中的 SQL 查询问题

python - NoReverseMatch 在/论坛/