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

基于Django Wagtail实现按用户类型与角色的动态菜单及CRUD权限控制

问题描述

我正在使用Django-Wagtail及Wagtail Menus构建系统,需求如下:

  • 用户分为Vendor、Supplier两类;
  • 所有用户必须归属其中一类;
  • 两类用户拥有不同角色,如Vendor包含finance、stock、admin,Supplier额外增加driver角色。

我已实现UserType基类,Vendor、Supplier继承该类,并在User模型中添加关联UserType的外键字段,同时通过Wagtail Admin为两类用户创建了对应自定义角色。

不同用户类型及下属角色的CRUD权限存在差异,我希望实现对应动态菜单,初步思路是基于MenuPage添加用户类型选择字段,现提出两个问题:

  1. 当前实现动态菜单的思路是否正确?若正确,如何针对用户类型下的角色做同样处理?
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 00:25:26