You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django深度嵌套related_set过滤查询报错,求正确实现方案

Django多对多关联下分类嵌套查询的问题解决

问题背景

现有两个Django模型:

class Subject(Model):
    categories = ManyToManyField(Category, related_name='subjects')

class Category(Model):
    parent = ForeignKey('self', related_name='subcategories')

需要生成嵌套结构的REST输出,要求每个分类的子分类必须与所属Subject存在多对多关联:

[
    {
        ...,
        "categories": [
            {"subcategories": [{}, {} ... {}]},
            {"subcategories": [{}, {} ... {}]}
        ],
    },
    {
        ...,
        "categories": [
            {"subcategories": [{}, {} ... {}]},
            {"subcategories": [{}, {} ... {}]}
        ],
    }
]

当前尝试的查询集触发错误:

Subject.objects.prefetch_related(
    Prefetch(
        lookup="categories",
        queryset=Category.objects.filter(parent__isnull=True)
        .prefetch_related(
            Prefetch(
                lookup="subcategories",
                queryset=Subquery(
                    Category.objects.filter(
                        parent__pk=OuterRef("pk"),
                        subjects__id__in=OuterRef(OuterRef("pk")),
                    )
                )
            )
        )
        .distinct(),
    ),
)

报错信息:

AttributeError: 'Subquery' object has no attribute '_chain'

错误原因分析

  1. Prefetch参数类型错误:Prefetch的queryset参数要求传入Django QuerySet实例,但你传入了Subquery对象。Subquery是用于SQL嵌套子查询的工具,本身不是QuerySet,不具备QuerySet的_chain等属性,这是直接触发报错的原因。
  2. 双层OuterRef引用无效:OuterRef(OuterRef("pk"))的用法不符合Django ORM的解析逻辑,在当前嵌套的Prefetch层级中,无法直接引用外层两层的Subject主键,这种跨层级引用无法被批量预取逻辑支持。

正确实现方式

方式一:使用Exists子查询实现预取

通过Exists子查询明确过滤出与当前Subject关联的子分类,同时保留批量预取的性能优势:

from django.db.models import Exists, OuterRef

Subject.objects.prefetch_related(
    Prefetch(
        lookup="categories",
        queryset=Category.objects.filter(parent__isnull=True).prefetch_related(
            Prefetch(
                lookup="subcategories",
                queryset=Category.objects.filter(
                    # 子分类的父级为当前顶级分类
                    parent=OuterRef('pk'),
                    # 子分类与外层Subject存在多对多关联
                    Exists(
                        Subject.objects.filter(
                            pk=OuterRef(OuterRef('pk')),
                            categories=OuterRef('pk')
                        )
                    )
                ).distinct()
            )
        ).distinct(),
    )
)

方式二:利用反向多对多关联过滤

借助Category与Subject的反向关联subjects,直接过滤出关联当前Subject的子分类:

from django.db.models import OuterRef

Subject.objects.prefetch_related(
    Prefetch(
        lookup="categories",
        queryset=Category.objects.filter(parent__isnull=True).prefetch_related(
            Prefetch(
                lookup="subcategories",
                queryset=Category.objects.filter(
                    parent=OuterRef('pk'),
                    # 子分类的subjects包含当前Subject
                    subjects__pk=OuterRef(OuterRef('pk'))
                ).distinct()
            )
        ).distinct(),
    )
)

配合DRF序列化器实现输出

如果使用Django REST Framework,需要配置嵌套序列化器来生成目标结构:

from rest_framework import serializers

class CategorySerializer(serializers.ModelSerializer):
    subcategories = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ['id', 'name', 'subcategories']  # 根据实际模型字段调整

    def get_subcategories(self, obj):
        # 从视图上下文获取当前Subject实例
        subject = self.context['view'].get_object()
        # 过滤出与该Subject关联的子分类
        valid_subcats = obj.subcategories.filter(subjects=subject)
        return CategorySerializer(valid_subcats, many=True).data

class SubjectSerializer(serializers.ModelSerializer):
    categories = CategorySerializer(many=True)

    class Meta:
        model = Subject
        fields = ['id', 'name', 'categories']  # 根据实际模型字段调整

注:如果使用序列化器的方式,建议结合上述预取查询集使用,避免N+1查询问题。

内容的提问来源于stack exchange,提问作者Михаил Павлов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:32:06