如何在Django中基于MySQL实现全应用级角色绑定动态菜单?
我来帮你搞定这个跨应用的动态角色菜单问题,刚好我之前做过类似的需求,结合你现有的三张MySQL表,咱们一步步来:
实现跨应用的基于角色的动态菜单方案
1. 先把MySQL表映射成Django模型
因为你已经有现成的表,不用从头建,可以先用python manage.py inspectdb生成基础模型,再手动调整关联关系(适配你的表结构):
# 建议放在公共app里,比如common或auth_ext/models.py from django.db import models from django.contrib.auth.models import AbstractUser class TblRole(models.Model): role_id = models.AutoField(primary_key=True) role_name = models.CharField(max_length=50, unique=True) role_desc = models.CharField(max_length=200, null=True, blank=True) class Meta: db_table = 'tbl_role' verbose_name = '角色' verbose_name_plural = '角色' class TblMenu(models.Model): menu_id = models.AutoField(primary_key=True) menu_name = models.CharField(max_length=100) parent_id = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', db_column='parent_id') url_name = models.CharField(max_length=200, null=True, blank=True) # 存Django视图的name,比如"app:view_name" icon = models.CharField(max_length=50, null=True, blank=True) # 菜单图标,比如FontAwesome类名 is_active = models.BooleanField(default=True) permission_codename = models.CharField(max_length=100, null=True, blank=True) # 关联Django权限,比如"app.view_model" class Meta: db_table = 'tbl_menu' verbose_name = '菜单' verbose_name_plural = '菜单' class TblRoleMenuMapping(models.Model): mapping_id = models.AutoField(primary_key=True) role = models.ForeignKey(TblRole, on_delete=models.CASCADE, db_column='role_id') menu = models.ForeignKey(TblMenu, on_delete=models.CASCADE, db_column='menu_id') class Meta: db_table = 'tbl_role_menu_mapping' unique_together = ('role', 'menu') # 避免重复绑定 # 如果你用自定义用户模型,记得关联角色(一个用户可以有多个角色) class User(AbstractUser): roles = models.ManyToManyField(TblRole, related_name='users', blank=True)
2. 全局上下文处理器:让所有模板自动获取菜单
要实现跨应用的菜单,最省心的方式是用上下文处理器——它能把菜单数据注入到所有模板的上下文里,不用每个视图都手动传参。
在公共app里创建context_processors.py:
# common/context_processors.py from .models import TblMenu, TblRoleMenuMapping def dynamic_role_menu(request): user_menu_tree = [] if request.user.is_authenticated: # 获取当前用户的所有角色 user_roles = request.user.roles.all() # 通过中间表拿到用户有权限的所有菜单ID allowed_menu_ids = TblRoleMenuMapping.objects.filter(role__in=user_roles).values_list('menu__menu_id', flat=True).distinct() # 筛选出激活的菜单,按层级整理 all_menus = TblMenu.objects.filter(menu_id__in=allowed_menu_ids, is_active=True).order_by('menu_id') # 把菜单整理成树形结构(父菜单包含子菜单) menu_dict = {menu.menu_id: menu for menu in all_menus} for menu in all_menus: # 过滤掉用户无对应权限的菜单 if menu.permission_codename and not request.user.has_perm(menu.permission_codename): continue if menu.parent_id: parent_menu = menu_dict.get(menu.parent_id_id) if parent_menu: if not hasattr(parent_menu, 'child_menus'): parent_menu.child_menus = [] parent_menu.child_menus.append(menu) else: user_menu_tree.append(menu) return {'dynamic_menu': user_menu_tree}
然后在settings.py里注册这个处理器:
TEMPLATES = [ { ... 'OPTIONS': { 'context_processors': [ ... 'common.context_processors.dynamic_role_menu', # 添加这一行 ], }, }, ]
3. 模板递归渲染层级菜单
菜单是树形结构,用递归模板片段来渲染最方便,避免重复代码。创建common/templates/common/menu_fragment.html:
<!-- 递归渲染菜单的模板片段 --> {% for menu in menu_list %} <li class="nav-item"> {% if menu.child_menus %} <!-- 有子菜单的情况,做下拉折叠 --> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="{{ menu.icon }}"></i> {{ menu.menu_name }} </a> <ul class="dropdown-menu"> {% include 'common/menu_fragment.html' with menu_list=menu.child_menus %} </ul> {% else %} <!-- 无子菜单,直接跳转 --> <a class="nav-link" href="{% if menu.url_name %}{% url menu.url_name %}{% else %}#{% endif %}"> <i class="{{ menu.icon }}"></i> {{ menu.menu_name }} </a> {% endif %} </li> {% endfor %}
最后在你的基础模板(比如base.html)里引用这个片段:
<!-- base.html --> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" href="/">项目首页</a> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ms-auto"> {% include 'common/menu_fragment.html' with menu_list=dynamic_menu %} </ul> </div> </div> </nav>
4. 关键优化点
- URL灵活性:菜单里存视图的
name而非硬编码URL,用{% url menu.url_name %}生成链接,适配不同app的路由。 - 权限精准控制:通过
permission_codename字段关联Django内置权限,用户无权限的菜单直接不显示。 - 多角色兼容:一个用户绑定多个角色时,会自动合并所有角色的菜单(去重)。
这样不管你新增多少个app,只要在tbl_menu里配置好菜单,给角色绑定对应的条目,用户登录后就能看到自己专属的动态菜单了,完全跨应用生效。
内容的提问来源于stack exchange,提问作者SHIV KUMAR
相关产品推荐
相关产品推荐

