django自带权限系统仅支持模型级控制,无法实现对象级权限;django-guardian通过将权限绑定到具体数据库实例解决此问题,需在model meta中声明权限、确保对象已保存、合理使用assign_perm/bulk_assign_perm,并优化admin集成与查询性能。

为什么直接用 django.contrib.auth 做不了对象级权限?
Django 自带的权限系统只支持模型级(model-level)控制,比如 can_add_user 或 can_change_article,但没法区分“张三能编辑自己写的 Article #123,但不能动李四的 Article #456”。django-guardian 就是为填这个空来的——它把权限绑定到具体数据库实例上,而不是整个 Article 模型。
常见错误现象:你在视图里调用 request.user.has_perm('change_article', article) 却返回 False,即使你刚用 assign_perm 赋过权——大概率是因为没在 Article 的 Meta 里加 permissions = [('change_article', 'Can change article')],或者漏了 guardian.shortcuts.assign_perm 的调用时机(比如在 model save() 之后才赋权,但用户已刷新页面)。
怎么给一个具体对象赋权?别只靠 assign_perm
assign_perm 是最常用入口,但它只是起点。真正落地得考虑谁来赋、何时赋、怎么查。
- 赋权前确认目标对象已保存到 DB(
article.pk必须存在),否则assign_perm('change_article', user, article)会静默失败 - 批量赋权时优先用
bulk_assign_perm,避免 N+1 查询;单次操作用assign_perm更清晰 - 检查权限别只信
has_perm,它不自动处理 group 权限继承——得确保用户所属 group 也通过assign_perm获得了该对象权限,或启用GUARDIAN_GET_INIT_ANONYMOUS_USER配置 - 删除对象前记得调用
remove_perm('delete_article', user, article),否则残留权限可能干扰新同名对象
如何在 Django Admin 里启用对象级权限控制?
默认 Admin 完全无视 django-guardian,必须手动接管权限逻辑。
关键动作是重写 ModelAdmin 的 get_queryset 和 has_change_permission:
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(
id__in=get_objects_for_user(request.user, 'myapp.change_article')
)
<p>def has_change_permission(self, request, obj=None):
if obj is None:
return True # 列表页允许进入,但数据已被 get_queryset 过滤
return request.user.has_perm('myapp.change_article', obj)</p>
注意:get_objects_for_user 返回的是 QuerySet,不是 ID 列表;如果模型有大量对象,建议配合数据库索引(如在 GuardianObjectPermission 表上建 (content_type_id, object_pk, permission_id) 复合索引)。
为什么 get_objects_for_user 查询慢?怎么优化?
这是生产环境最常踩的坑:get_objects_for_user 默认走 JOIN 查询,当用户有几十个 group、每个 group 有上百条对象权限时,SQL 可能生成上百行嵌套子查询,响应时间从毫秒变秒级。
提速方案不是换库,而是控制权限粒度:
- 避免给用户直接赋对象权限,改用 Group + 对象权限组合(Group 成员变动时统一刷新)
- 用
get_objects_for_user(..., accept_global_perms=False)显式关闭全局权限检查,除非你真需要混合模型级+对象级判断 - 缓存结果:对不常变动的对象权限,用
cache.get_or_set(f'perms_{user.id}_{perm}', ...)包一层,key 里带上perm和user.id - 不要在模板里反复调用
{% if perms.myapp.change_article %}—— 它查的是模型级权限,和对象无关;对象级必须显式传article实例进去
对象级权限本身不复杂,难的是权限边界的定义是否清晰——比如“编辑自己创建的文章”和“编辑所在部门所有文章”,前者靠 post_save 信号自动赋权,后者就得在部门变更时批量更新权限,逻辑一多就容易漏掉清理路径。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











