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对应的权限了。
测试一下
现在你可以这样验证整个流程:
- 创建一个绑定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") - 给用户添加该权限:
user.add_client_permission("view_order", "client_001", "orders") - 在视图中设置
request.session['ClientId'] = "client_001",调用user.has_perm("orders.view_order")会返回True - 模板里
{% if perms.orders.view_order %}会正常显示对应内容
内容的提问来源于stack exchange,提问作者Bakri A. Altaif

