为什么我的所有 SQL 查询都被 Django 重复 4 次,使用 "Prefetch_related" 嵌套 MPTT children?

Why are all my SQL queries being duplicated 4 times for Django using "Prefetch_related" for nested MPTT children?

我有一个 Child 自身具有外键的 MPTT 模型:

class Child(MPTTModel):
    title = models.CharField(max_length=255)
    parent = TreeForeignKey(
        "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
    )

我有一个递归序列化程序,因为我想为任何给定的 Child:

显示所有级别的 children
class ChildrenSerializer(serializers.HyperlinkedModelSerializer):
    url = HyperlinkedIdentityField(
        view_name="app:children-detail", lookup_field="pk"
    )

    class Meta:
        model = Child
        fields = ("url", "title", "children")

    def get_fields(self):
        fields = super(ChildrenSerializer, self).get_fields()
        fields["children"] = ChildrenSerializer(many=True)
        return fields

我正在尝试减少访问 Child 的 DetailView 时进行的 duplicate/similar 查询的数量。

下面的视图适用于深度 2 - 但是,“深度”并不总是已知的或静态的。

class ChildrenDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Child.objects.prefetch_related(
        "children",
        "children__children",
        # A depth of 3 will additionally require "children__children__children",
        # A depth of 4 will additionally require "children__children__children__children",
        # etc.
    )
    serializer_class = ChildrenSerializer
    lookup_field = "pk"

注意:如果我不使用 prefetch_related 而只是将查询集设置为 Child.objects.all(),每个 SQL 查询都会重复四次...我不知道为什么。

如何利用 Child 的深度(即 Child 的 MPTT level 字段)来优化预取?我应该覆盖视图的 get_object and/or retrieve 吗?

如果我在预取中添加多得离谱的深度,这有什么关系吗?例如。 children__children__children__children__children__children__children__children?它似乎并没有增加不需要该深度级别的 Children objects 的查询数量。

编辑:

嗯,不知道为什么,但是当我尝试序列化任何 Child 的顶部 parent(即 MPTT 的 get_root)时,它重复了 SQL 查询四次???

class Child(MPTTModel):
    ...
    @property
    def top_parent(self):
        return self.get_root()
    

class ChildrenSerializer(serializers.HyperlinkedModelSerializer):
    ...
    top_parent = ParentSerializer()
    fields = ("url", "title", "children", "top_parent")

编辑 2

添加任意 SerializerMethodField 确认它被查询了四次...出于某种原因?例如

class ChildrenSerializer(serializers.HyperlinkedModelSerializer):
    ...
    foo = serializers.SerializerMethodField()

    def get_foo(self, obj):
        print("bar")
        return obj.get_root().title

这将打印“bar”四次。 SQL查询也根据django-debug-toolbar:

重复四次

SELECT ••• FROM "app_child" WHERE ("app_child"."parent_id" IS NULL AND "app_child"."tree_id" = '7') LIMIT 21

4 similar queries. Duplicated 4 times.

您是否在使用 DRF 的可浏览 API?它在 rest_framework.renderers.BrowsableAPIRenderer.get_context.

中为 HTML 表单再初始化序列化程序 3 次

如果您使用 Postman 执行相同的请求,“bar”应该只打印一次。