基于Django Wagtail实现按用户类型与角色的动态菜单及CRUD权限控制
问题描述
我正在使用Django-Wagtail及Wagtail Menus构建系统,需求如下:
- 用户分为Vendor、Supplier两类;
- 所有用户必须归属其中一类;
- 两类用户拥有不同角色,如Vendor包含finance、stock、admin,Supplier额外增加driver角色。
我已实现UserType基类,Vendor、Supplier继承该类,并在User模型中添加关联UserType的外键字段,同时通过Wagtail Admin为两类用户创建了对应自定义角色。
不同用户类型及下属角色的CRUD权限存在差异,我希望实现对应动态菜单,初步思路是基于MenuPage添加用户类型选择字段,现提出两个问题:
- 当前实现动态菜单的思路是否正确?若正确,如何针对用户类型下的角色做同样处理?
- CRUD操作是否需通过代码每次检查用户权限?
相关代码
用户模型代码
class UserType(models.Model): # 通用字段 ... class Vendor(UserType): # Vendor专属字段 ... class Supplier(UserType): # Supplier专属字段 ... class User(AbstractUser): user_type = models.ForeignKey(UserType, on_delete=models.SET_NULL, null=True, blank=True)
菜单页面代码
USER_TYPE_CHOICES = ( ('Vendor', 'Vendor'), ('Supplier', 'Supplier') ) class GenericPage(MenuPage): """ 该模型继承MenuPage的字段、方法和setting_panels属性, 重写settings_panels以在“设置”标签中包含其他字段 """ available_to = models.CharField(choices=USER_TYPE_CHOICES,) settings_panels = [ FieldPanel('custom_settings_field_one'), menupage_panel ]
解决方案
问题1:动态菜单实现思路及角色级处理
你的基础思路是可行的,在此基础上可以扩展以支持角色级别的菜单控制:
1.1 思路正确性验证
通过给MenuPage添加用户类型字段来控制菜单可见性,是Wagtail Menus实现动态菜单的常规方案之一,能够满足用户类型维度的权限控制需求,思路没问题。
1.2 角色级扩展实现
要支持角色级的菜单控制,需要做以下调整:
- 扩展GenericPage模型:添加角色关联字段,直接关联Wagtail的Group模型(Wagtail的角色本质是Group),支持多选:
from wagtail.models import Group class GenericPage(MenuPage): available_to = models.CharField(choices=USER_TYPE_CHOICES, blank=True) available_roles = models.ManyToManyField(Group, blank=True, verbose_name="可见角色") settings_panels = [ FieldPanel('custom_settings_field_one'), FieldPanel('available_to'), FieldPanel('available_roles'), menupage_panel ] - 自定义菜单渲染逻辑:继承Wagtail Menus的菜单类,重写
get_menu_items方法,同时校验用户类型和所属角色:from wagtailmenus.models import MainMenu class CustomMainMenu(MainMenu): def get_menu_items(self, request): items = super().get_menu_items(request) user = request.user filtered_items = [] if not user.is_authenticated: return filtered_items # 获取用户类型(类名)和所属角色名称 user_type = user.user_type.__class__.__name__ if user.user_type else None user_roles = set(user.groups.values_list('name', flat=True)) for item in items: page = item.link_page # 优先校验角色权限,再校验用户类型 if page.available_roles.exists(): page_roles = set(page.available_roles.values_list('name', flat=True)) if not user_roles & page_roles: continue if page.available_to and page.available_to != user_type: continue filtered_items.append(item) return filtered_items - 模板层快速控制:如果不需要自定义菜单类,也可以直接在菜单模板中添加判断逻辑(以
main_menu.html为例):{% for item in menu_items %} {% with page=item.link_page %} {% if not page.available_to or page.available_to == user.user_type.__class__.__name__ %} {% if not page.available_roles.exists or user.groups.all|intersect:page.available_roles.all %} <li><a href="{{ item.href }}">{{ item.text }}</a></li> {% endif %} {% endif %} {% endwith %} {% endfor %}
问题2:CRUD操作的权限检查
不需要每次手动编写重复的权限检查代码,可通过以下方式优化:
- 利用Wagtail内置权限系统:Wagtail的Group(角色)本身支持对模型、页面的CRUD权限配置。在Wagtail Admin的
Settings > Groups中,可直接给不同角色分配特定页面类型或模型的添加、编辑、删除权限,这部分配置会自动生效,无需额外代码。 - 自定义权限复用逻辑:针对复杂业务规则(如基于用户类型的额外校验),可以自定义装饰器或视图mixins,复用权限检查逻辑:
from django.contrib.auth.decorators import user_passes_test from django.utils.decorators import method_decorator def has_vendor_admin_permission(user): return user.is_authenticated and isinstance(user.user_type, Vendor) and 'admin' in user.groups.values_list('name', flat=True) @method_decorator(user_passes_test(has_vendor_admin_permission), name='dispatch') class VendorAdminOnlyView(View): # 视图逻辑 ... - 模型层权限控制:重写模型的
has_add_permission、has_change_permission等方法,实现基于用户类型和角色的细粒度权限控制:class Vendor(models.Model): def has_change_permission(self, request): user = request.user return user.is_authenticated and isinstance(user.user_type, Vendor) and 'admin' in user.groups.values_list('name', flat=True)
内容的提问来源于stack exchange,提问作者sqwale
相关产品推荐
相关产品推荐

