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

Django Admin复杂用户权限建模:多Unit成员与跨部门权限方案

问题描述

现有Django实体的一对多层级结构:

- Company
  - Division
    - Department
      - Unit

原权限体系里,每个用户只属于一个Unit,通过给用户分配DepartmentAdmin、DivisionAdmin这类组,就能在Django Admin里管理对应实体的增删改权限——只要顺着Unit往上追溯层级就能判定权限,逻辑简单得很。

现在要重构权限体系:用户可以同时属于多个Unit(主Unit可以保留),而且能拥有任意Department、Division甚至Company层级的管理权限。

之前试过用Membership关联表来存储角色和权限附加信息,但这个方案会产生一堆依赖,权限判定逻辑也绕得慌,示例代码如下:

class Membership(models.Model):
    user = models.ForeignKey(User,
       on_delete=models.CASCADE, related_name="other_departments")
    department = models.ForeignKey(Department, 
       on_delete=models.CASCADE, related_name="other_members")
    position = models.CharField(_("Position"), max_length=32, null=True, blank=True)

想找个更优的方案,最好能复用Django自带的权限管理功能,求实现思路。

最优实现思路(基于Django原生权限体系)

1. 扩展用户与Unit的关联关系

先保留用户的main_unit外键(可选,用来兼容原有业务逻辑),同时新增多对多字段让用户能关联多个Unit:

from django.contrib.auth.models import AbstractUser
from django.db import models

class Unit(models.Model):
    # 原有字段(比如名称、所属部门等)
    members = models.ManyToManyField(AbstractUser, related_name="units", blank=True)

class User(AbstractUser):
    main_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, related_name="primary_users")
    # 其他自定义字段按需添加

这样用户直接就能关联多个Unit,除非需要存储加入时间这类额外属性,否则不用额外搞中间表——真要加的话,用through参数扩展中间表就行,尽量别搞复杂。

2. 用原生Group+Permission实现带范围的层级权限

Django自带的Group和Permission完全能适配这个需求,不用重新造轮子:

  • 定义带范围的权限组:创建CompanyAdmin、DivisionAdmin、DepartmentAdmin、UnitAdmin这些组,每个组绑定对应实体的add/change/delete权限(Django会自动给每个Model生成这些基础权限,也可以自己定义特殊权限)。
  • 给权限组绑定具体实体:扩展Group模型,加外键关联对应的层级实体,让权限组只作用于特定的业务范围:
from django.contrib.auth.models import Group
from django.db import models

class ScopedGroup(Group):
    # 同一组只能关联一个层级的实体,所以用null=True+blank=True做互斥
    company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True, blank=True)
    division = models.ForeignKey(Division, on_delete=models.CASCADE, null=True, blank=True)
    department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True, blank=True)
    unit = models.ForeignKey(Unit, on_delete=models.CASCADE, null=True, blank=True)

    class Meta:
        verbose_name = "带范围权限组"

比如创建一个DepartmentAdmin组,关联到“市场部”,那加入这个组的用户就只拥有市场部的管理权限,不会影响其他部门。

3. 封装自定义权限判定逻辑

在视图或者Admin里,通过以下逻辑判断用户有没有权限操作某个实体:
拿Department对象dept举例:

  1. 先查用户是否属于绑定了该dept的DepartmentAdmin组;
  2. 没有的话,往上查是否属于绑定了dept.division的DivisionAdmin组;
  3. 再没有就查是否属于绑定了dept.division.company的CompanyAdmin组;
  4. 同时,用户自己关联的Unit对应的上层实体权限组,也要纳入判定范围。

把这个逻辑封装成可复用的方法:

from django.db.models import Q

def has_hierarchy_perm(user, obj, perm_type):
    # perm_type 传 'add'/'change'/'delete' 就行
    perm_codename = f"{obj._meta.app_label}.{perm_type}_{obj._meta.model_name}"
    
    # 检查用户是否有该实体的直接权限组
    filter_kwargs = {}
    if isinstance(obj, Department):
        filter_kwargs["department"] = obj
    elif isinstance(obj, Division):
        filter_kwargs["division"] = obj
    elif isinstance(obj, Company):
        filter_kwargs["company"] = obj
    elif isinstance(obj, Unit):
        filter_kwargs["unit"] = obj
    
    if filter_kwargs and user.groups.filter(**filter_kwargs).filter(permissions__codename=perm_codename).exists():
        return True
    
    # 向上递归检查父层级的权限
    if hasattr(obj, 'department'):
        return has_hierarchy_perm(user, obj.department, perm_type)
    elif hasattr(obj, 'division'):
        return has_hierarchy_perm(user, obj.division, perm_type)
    elif hasattr(obj, 'company'):
        return has_hierarchy_perm(user, obj.company, perm_type)
    
    # 最后检查用户是否有全局权限(比如超级用户)
    return user.has_perm(perm_codename)

4. Django Admin适配调整

在Admin类里重写权限相关方法,调用上面的自定义判定逻辑,同时在get_queryset里过滤出用户有权限操作的实体:

from django.contrib import admin

class DepartmentAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        
        # 过滤用户能管理的Department:自己关联的Unit所属部门、权限组绑定的部门、父层级权限组覆盖的部门
        user_units = request.user.units.all()
        user_depts = Department.objects.filter(unit__in=user_units).distinct()
        
        # 权限组直接绑定的部门
        group_depts = Department.objects.filter(id__in=request.user.groups.values_list('department_id', flat=True)).distinct()
        
        # 权限组绑定的事业部下属部门
        group_divisions = Division.objects.filter(id__in=request.user.groups.values_list('division_id', flat=True)).distinct()
        division_depts = Department.objects.filter(division__in=group_divisions).distinct()
        
        # 权限组绑定的公司下属部门
        group_companies = Company.objects.filter(id__in=request.user.groups.values_list('company_id', flat=True)).distinct()
        company_depts = Department.objects.filter(division__company__in=group_companies).distinct()
        
        return qs.filter(id__in=user_depts | group_depts | division_depts | company_depts)
    
    def has_change_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        if obj is None:
            # 没有具体对象时,检查用户是否有权限管理任何部门
            return self.get_queryset(request).exists()
        return has_hierarchy_perm(request.user, obj, 'change')
    
    # 同理重写 has_add_permission 和 has_delete_permission
    def has_add_permission(self, request):
        if request.user.is_superuser:
            return True
        # 新增权限可以根据用户的层级权限组判定,比如允许DivisionAdmin在自己的事业部下新增部门
        return request.user.groups.filter(
            Q(division__isnull=False) | Q(company__isnull=False) | Q(department__isnull=False)
        ).filter(permissions__codename=f"{Department._meta.app_label}.add_department").exists()
    
    def has_delete_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        if obj is None:
            return self.get_queryset(request).exists()
        return has_hierarchy_perm(request.user, obj, 'delete')

内容的提问来源于stack exchange,提问作者Mundi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 08:57:06