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

如何通过Django ORM结合DRF生成自关联模型的层级JSON?

问题描述

使用Django REST Framework(DRF),需要通过Django ORM获取层级结构的分类JSON数据,纠结应使用Category.objects.filter(active=True).select_related()还是prefetch_related(),同时提供了模型、序列化器代码及期望的JSON输出格式:

模型定义

class Category(models.Model):
    parent = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True)
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True, default='', blank=True)
    active = models.BooleanField(default=True)

原序列化器定义

class ParentCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'

class CategoryListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['name', 'slug', 'parent']

    parent = ParentCategorySerializer(many=True)

期望输出格式

[
    {
        "name": "First category",
        "slug": "first-category",
        "subcategories": [
            {
                "name": "First subcategory one",
                "slug": "first-subcategory-one",
                "subcategories": []
            },
            {
                "name": "First subcategory two",
                "slug": "first-subcategory-two",
                "subcategories": []
            }
        ]
    },
    {
        "name": "Second category",
        "slug": "second-category",
        "subcategories": []
    }
]

解决方案

1. 先明确select_related与prefetch_related的适用场景

  • select_related:用于正向外键/一对一关系,通过SQL JOIN一次性拉取关联数据,适合从子节点查询父节点(比如查某个分类的父分类)。
  • prefetch_related:用于反向外键/多对多关系,分两次查询(先查主表,再查关联表匹配),适合从父节点查询子节点(比如查某个分类的所有子分类)。

你的场景是要构建父分类包含子分类的层级结构,因此需要用prefetch_related。

2. 模型优化(可选但推荐)

给parent字段添加related_name,让反向查询子分类的语义更清晰:

class Category(models.Model):
    parent = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, related_name='subcategories')
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True, default='', blank=True)
    active = models.BooleanField(default=True)

3. 序列化器调整(实现递归层级)

原序列化器逻辑方向错误(把父节点当成多对多关系处理),需要用递归序列化器实现层级嵌套:

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

    class Meta:
        model = Category
        fields = ['name', 'slug', 'subcategories']

    def get_subcategories(self, obj):
        # 只返回激活的子分类,递归序列化深层级
        active_subcats = obj.subcategories.filter(active=True)
        return CategorySerializer(active_subcats, many=True).data if active_subcats.exists() else []

4. ORM查询写法

基础版(获取顶级分类+预取一级子分类)

如果只需要预取一级子分类,避免N+1查询:

from django.db.models import Prefetch

# 获取所有激活的顶级分类(parent为null),并预取其激活的子分类
top_categories = Category.objects.filter(
    parent__isnull=True, 
    active=True
).prefetch_related(
    Prefetch('subcategories', queryset=Category.objects.filter(active=True))
)

深层级处理说明

Django ORM原生不支持递归预取所有层级的子分类,若需要支持更深层级,有两种方案:

  • 方案1:使用上述递归序列化器,虽然会产生少量N+1查询,但如果分类层级不多,性能影响可忽略。
  • 方案2:引入django-tree-queries或django-mptt这类树形结构专用库,更高效地处理递归查询与层级构建。

5. 视图示例

from rest_framework.views import APIView
from rest_framework.response import Response

class CategoryTreeView(APIView):
    def get(self, request):
        top_categories = Category.objects.filter(parent__isnull=True, active=True)
        serializer = CategorySerializer(top_categories, many=True)
        return Response(serializer.data)

内容的提问来源于stack exchange,提问作者Виктор

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 01:05:29