如何通过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,提问作者Виктор
相关产品推荐
相关产品推荐

