如何在Django中构建带REST API的多级动态菜单?
多级动态菜单的Model与Serializer设计方案
一、Model优化设计
你用自关联ForeignKey实现树形菜单的思路完全正确,这是处理层级结构数据的经典方案。下面补充几个实用字段,让菜单功能更完善:
from django.db import models class MenuPoint(models.Model): name = models.CharField(max_length=200, verbose_name="菜单名称") url = models.CharField(max_length=200, blank=True, null=True, verbose_name="跳转链接") icon = models.CharField(max_length=100, blank=True, null=True, verbose_name="菜单图标类名") order = models.IntegerField(default=0, verbose_name="排序权重") # 控制同级菜单展示顺序 is_active = models.BooleanField(default=True, verbose_name="是否启用") parent_menu_point = models.ForeignKey( 'self', on_delete=models.CASCADE, related_name='submenus', # 改成更语义化的名称,后续序列化更直观 blank=True, null=True, verbose_name="父级菜单" ) class Meta: verbose_name = "菜单项" verbose_name_plural = "菜单项" ordering = ['order'] # 默认按排序权重升序排列 def __str__(self): return self.name
补充字段说明:
url:存储菜单跳转的路由或外部链接,前端渲染时直接调用icon:存储图标类名(比如el-icon-menu),方便前端展示菜单图标order:解决同级菜单的排序问题,数值越小展示越靠前is_active:无需删除数据即可控制菜单是否在前端显示- 调整
related_name为submenus,比原命名更清晰,符合语义
二、Serializer设计(递归嵌套实现多级菜单)
要输出层级化的菜单结构,需要使用递归Serializer,Django REST Framework原生支持这种写法:
from rest_framework import serializers from .models import MenuPoint class MenuPointSerializer(serializers.ModelSerializer): # 递归引用自身,序列化子菜单 submenus = serializers.SerializerMethodField() class Meta: model = MenuPoint fields = ['id', 'name', 'url', 'icon', 'order', 'is_active', 'submenus'] def get_submenus(self, obj): # 仅序列化启用状态的子菜单,并按排序权重排序 active_submenus = obj.submenus.filter(is_active=True).order_by('order') return MenuPointSerializer(active_submenus, many=True, context=self.context).data
视图层处理(返回顶级菜单)
在视图中只需查询父级为null的顶级菜单,Serializer会自动递归嵌套所有子菜单:
from rest_framework.views import APIView from rest_framework.response import Response from django.db.models import Prefetch from .models import MenuPoint from .serializers import MenuPointSerializer class MenuListAPIView(APIView): def get(self, request): # 预加载子菜单,减少数据库查询次数(优化性能) top_level_menus = MenuPoint.objects.filter( parent_menu_point__isnull=True, is_active=True ).order_by('order').prefetch_related( Prefetch('submenus', queryset=MenuPoint.objects.filter(is_active=True).order_by('order')) ) serializer = MenuPointSerializer(top_level_menus, many=True) return Response(serializer.data)
三、返回效果示例
接口返回的JSON结构会完全匹配你需要的多级菜单格式:
[ { "id": 1, "name": "Menu 1", "url": "/menu1", "icon": "el-icon-menu", "order": 0, "is_active": true, "submenus": [ { "id": 2, "name": "Submenu 1.1", "url": "/menu1/sub1", "icon": "el-icon-s-menu", "order": 0, "is_active": true, "submenus": [ { "id": 3, "name": "Submenu 1.1.1", "url": "/menu1/sub1/sub1", "icon": "", "order": 0, "is_active": true, "submenus": [] }, { "id": 4, "name": "Submenu 1.1.2", "url": "/menu1/sub1/sub2", "icon": "", "order": 1, "is_active": true, "submenus": [] } ] }, { "id": 5, "name": "Submenu 1.2", "url": "/menu1/sub2", "icon": "el-icon-s-menu", "order": 1, "is_active": true, "submenus": [] } ] }, { "id": 6, "name": "Menu 2", "url": "/menu2", "icon": "el-icon-menu", "order": 1, "is_active": true, "submenus": [] } ]
四、注意事项
- 如果菜单层级极深(超过10层),递归序列化可能引发性能问题,建议用
prefetch_related预加载所有子菜单,减少数据库查询次数 - 若需结合权限控制,可在
get_submenus方法中根据当前用户权限过滤子菜单 - 如果需要批量更新菜单排序,可考虑引入
django-mptt库优化树形结构的操作效率
内容的提问来源于stack exchange,提问作者prodoreda
相关产品推荐
相关产品推荐

