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

Django新手求助:如何为Permission模型添加client_id并实现权限校验

解决Django Permission模型添加client_id并实现多客户端权限校验的方案

嘿,作为Django新手碰到这种定制化权限的需求确实挺棘手的,我来一步步帮你搞定这个问题,让权限能和client_id绑定,并且校验时自动匹配当前客户端~

1. 自定义Permission模型,添加client_id字段

Django内置的auth.Permission模型是无法直接修改的,所以我们需要继承AbstractPermission来创建自己的权限模型,添加client_id字段:

# your_app/models.py
from django.db import models
from django.contrib.auth.models import AbstractPermission, ContentType

class ClientPermission(AbstractPermission):
    client_id = models.CharField(max_length=100, verbose_name="客户端ID")

    class Meta:
        verbose_name = "客户端权限"
        verbose_name_plural = "客户端权限"

然后在项目的settings.py里配置,让Django使用我们自定义的权限模型:

# settings.py
AUTH_PERMISSION_MODEL = "your_app.ClientPermission"

⚠️ 注意:如果你的项目已经做过数据库迁移,修改这个配置后需要重新初始化数据库(或者手动处理迁移),因为Django的权限模型是核心表,改动比较大。

2. 扩展User模型的权限添加方法,支持传入client_id

默认的user.user_permissions.add()只能接收权限对象,没法直接传入client_id。我们可以通过自定义User模型(推荐,更灵活)来实现这个需求:

# your_app/models.py
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    def add_client_permission(self, perm_codename, client_id, app_label):
        # 获取对应模型的ContentType,替换成你实际的模型
        content_type = ContentType.objects.get(app_label=app_label, model='your_model')
        # 查找或创建对应client_id的权限
        perm, created = ClientPermission.objects.get_or_create(
            codename=perm_codename,
            content_type=content_type,
            client_id=client_id,
            defaults={'name': f"{perm_codename} ({client_id})"}
        )
        self.user_permissions.add(perm)

然后在settings.py配置自定义User模型:

AUTH_USER_MODEL = "your_app.CustomUser"

这样你就可以像下面这样添加权限了:

user.add_client_permission("view_order", request.session['ClientId'], "orders")

如果不想自定义User模型,也可以用猴子补丁给默认User临时添加方法(应急可用,不推荐长期使用):

# your_app/apps.py
from django.apps import AppConfig
from django.contrib.auth.models import User

def add_client_permission(self, perm_codename, client_id, app_label):
    from your_app.models import ClientPermission, ContentType
    content_type = ContentType.objects.get(app_label=app_label, model='your_model')
    perm, created = ClientPermission.objects.get_or_create(
        codename=perm_codename,
        content_type=content_type,
        client_id=client_id,
        defaults={'name': f"{perm_codename} ({client_id})"}
    )
    self.user_permissions.add(perm)

class YourAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'your_app'

    def ready(self):
        User.add_to_class('add_client_permission', add_client_permission)

3. 修改权限校验逻辑,让has_perm支持client_id

默认的user.has_perm()方法只会校验权限的codename和content_type,我们需要重写它,让它同时匹配当前的client_id。

在自定义User模型里重写has_perm方法:

# your_app/models.py
class CustomUser(AbstractUser):
    # ... 其他字段和方法 ...

    def has_perm(self, perm, obj=None):
        # 用线程局部变量存储当前请求的client_id,需要配合中间件使用
        import threading
        current_client_id = threading.local().client_id if hasattr(threading.local(), 'client_id') else None

        if not current_client_id:
            # 没有client_id时的兜底逻辑,可根据需求调整
            return super().has_perm(perm, obj)

        # 拆分权限字符串格式:"app_label.codename"
        app_label, codename = perm.split('.', 1)
        return self.user_permissions.filter(
            codename=codename,
            content_type__app_label=app_label,
            client_id=current_client_id
        ).exists()

然后写一个中间件,自动从session中获取client_id并存储到线程局部变量:

# your_app/middleware.py
import threading

class ClientIdMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 从session获取client_id,也可根据实际从请求头、cookie等地方获取
        client_id = request.session.get('ClientId')
        if client_id:
            threading.local().client_id = client_id
        else:
            # 清空线程变量避免内存泄漏
            if hasattr(threading.local(), 'client_id'):
                del threading.local().client_id

        response = self.get_response(request)

        # 请求结束后清理线程变量
        if hasattr(threading.local(), 'client_id'):
            del threading.local().client_id

        return response

在settings.py的MIDDLEWARE里添加这个中间件:

MIDDLEWARE = [
    # ... 其他中间件 ...
    'your_app.middleware.ClientIdMiddleware',
]

4. 适配模板中的{% if perms.app_name.perm_code %}校验

默认的perms上下文变量不会考虑client_id,我们需要自定义上下文处理器替换它:

# your_app/context_processors.py
from django.contrib.auth.context_processors import PermWrapper as DefaultPermWrapper
import threading

class ClientPermWrapper(DefaultPermWrapper):
    def __getitem__(self, app_label):
        app_perms = super().__getitem__(app_label)
        client_id = threading.local().client_id if hasattr(threading.local(), 'client_id') else None
        if not client_id:
            return app_perms

        # 过滤出当前client_id对应的权限
        filtered_perms = [p for p in app_perms if self.user.user_permissions.filter(
            codename=p,
            content_type__app_label=app_label,
            client_id=client_id
        ).exists()]
        return self.__class__(self.user, app_label, filtered_perms)

def client_perms(request):
    return {
        'perms': ClientPermWrapper(request.user) if request.user.is_authenticated else DefaultPermWrapper(request.user)
    }

然后在settings.py的TEMPLATES里,替换默认的auth上下文处理器:

TEMPLATES = [
    {
        # ... 其他配置 ...
        'OPTIONS': {
            'context_processors': [
                # ... 其他上下文处理器 ...
                # 替换默认的'django.contrib.auth.context_processors.auth'
                'your_app.context_processors.client_perms',
            ],
        },
    },
]

这样模板里的{% if perms.orders.view_order %}就只会校验当前client_id对应的权限了。

测试一下

现在你可以这样验证整个流程:

  1. 创建一个绑定client_id的权限:
    content_type = ContentType.objects.get(app_label="orders", model="order")
    ClientPermission.objects.create(name="查看订单", codename="view_order", content_type=content_type, client_id="client_001")
    
  2. 给用户添加该权限:user.add_client_permission("view_order", "client_001", "orders")
  3. 在视图中设置request.session['ClientId'] = "client_001",调用user.has_perm("orders.view_order")会返回True
  4. 模板里{% if perms.orders.view_order %}会正常显示对应内容

内容的提问来源于stack exchange,提问作者Bakri A. Altaif

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:07:52