Django-guardian
为Django
实现Django
对象权限,提供身份验证后端,其扩展特性如下:
- 匿名用户支持
- 高级
API
Django
对象权限- 较全面的测试覆盖
- 装饰器支持
Django
的管理集成:Django-grappelli
安装
此应用程序需要 Django 2.2
或更高版本,这是使用 django-guardian
之前的唯一先决条件。
$ pip install django-guardian
配置
安装后,我们可以为对象权限处理准备我们的项目。在设置模块中,我们需要将监护人添加到 INSTALLED_APPS
:
INSTALLED_APPS = ( # ... 'guardian', )
并在guardian
权限后端添加钩子:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
)
一旦项目被配置为与 django-guardian
一起工作,调用 migrate
管理命令将为匿名用户支持创建用户实例(名称为 AnonymousUser
)。
Guardian
匿名用户不同于 Django
匿名用户。 Django
匿名用户在数据库中没有条目,但是 Guardian 匿名用户有。这意味着以下代码将返回意外结果:
from django.contrib.auth import get_user_model
User = get_user_model()
anon = User.get_anonymous()
anon.is_anonymous # returns False
我们可以将 id 更改为我们喜欢的任何内容。项目现在应该可以使用对象权限了。
可选设置
Guardian
具有以下可选配置变量:
GUARDIAN_RAISE_403
:新版本1.0.4
。如果设置为True
,guardian
将引发django.core.exceptions.PermissionDenied
错误,而不是返回空的django.http.HttpResponseForbidden
。GUARDIAN_RENDER_403
:新版本1.0.4
。如果设置为true
,监护人将尝试呈现403
响应,而不是返回无完整的django.http.httpresponsessonsefordforbidden
。将使用guardian_template_403
指向的模板来执行此操作。默认值为false
。- GUARDIAN_TEMPLATE_403:使用状态代码
403
(即cermission_required
)使用哪种模板用于响应。默认为403.html
。 ANONYMOUS_USER_NAME
:这是匿名用户的用户名。用于创建匿名用户并随后根据需要获取匿名用户。如果ANONYMOUS_USER_NAME
设置为None
,则禁用匿名用户对象权限。如果创建用户对象来表示匿名用户在您的环境中会出现问题,您可能需要选择此选项。GUARDIAN_GET_INIT_ANONYMOUS_USER
:Guardian
支持匿名用户的对象级权限,但是在我们的项目中使用自定义用户模型时,默认功能可能会失败。这可能会导致问题,因为监护人会在每次迁移调用后尝试创建匿名用户。使用此设置指向的函数检索将要创建的对象。一旦检索到,将在该实例上调用save
方法。
GUARDIAN_GET_CONTENT_TYPE
:Guardian 允许应用程序提供自定义函数来从对象和模型中检索内容类型。当类或类层次结构以非标准方式使用 ContentType 框架时,这很有用。大多数应用程序不必更改此设置。
GUARDIAN_AUTO_PREFETCH
:默认False
,对于使用标准ContentType
接口和默认UserObjectPermission
或GroupObjectPermission
模型的原始部署,Guardian
可以自动预取所有对象类型的所有用户权限。当由于大量模型类型导致 O(n) 查询而导致手动预取不可行时,这可能很有用。此设置可能与非标准部署不兼容,并且仅应在非预取调用会导致大量查询或延迟特别重要时使用。GUARDIAN_USER_OBJ_PERMS_MODEL
:默认'guardian.UserObjectPermission'
,允许自定义模型覆盖默认的UserObjectPermission
模型。自定义模型需要最低限度地继承自UserObjectPermissionAbstract
。这仅在项目开始时设置时自动支持。这在项目开始后不受支持。如果依赖库没有为模型调用UserObjectPermission = get_user_obj_perms_model()
,那么依赖库不支持该特性。
定义自定义用户对象权限模型:
from guardian.models import UserObjectPermissionAbstract
class BigUserObjectPermission(UserObjectPermissionAbstract):
id = models.BigAutoField(editable=False, unique=True, primary_key=True)
class Meta(UserObjectPermissionAbstract.Meta):
abstract = False
indexes = [
*UserObjectPermissionAbstract.Meta.indexes,
models.Index(fields=['content_type', 'object_pk', 'user']),
]
配置guardian
以在settings.py
中使用自定义模型:
GUARDIAN_USER_OBJ_PERMS_MODEL = 'myapp.BigUserObjectPermission'
要访问模型,请使用不带参数的 get_user_obj_perms_model()
from guardian.utils import get_user_obj_perms_model
UserObjectPermission = get_user_obj_perms_model()
GUARDIAN_GROUP_OBJ_PERMS_MODEL
:允许自定义模型覆盖默认的GroupObjectPermission
模型。自定义模型需要最低限度地继承自GroupObjectPermissionAbstract
。这仅在项目开始时设置时自动支持。这在项目开始后不受支持。如果依赖库没有为模型调用GroupObjectPermission = get_user_obj_perms_model()
,那么依赖库不支持该特性。
from guardian.models import GroupObjectPermissionAbstract
class BigGroupObjectPermission(GroupObjectPermissionAbstract):
id = models.BigAutoField(editable=False, unique=True, primary_key=True)
class Meta(GroupObjectPermissionAbstract.Meta):
abstract = False
indexes = [
*GroupObjectPermissionAbstract.Meta.indexes,
models.Index(fields=['content_type', 'object_pk', 'group']),
]
示例
分配对象权限
为模型创建权限后,分配对象权限应该非常简单。
class Task(models.Model):
summary = models.CharField(max_length=32)
content = models.TextField()
reported_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
permissions = (
('assign_task', 'Assign task'),
)
在信号中分配权限
请注意,匿名用户是在创建权限之前创建的。这可能会导致 Django
信号,例如在创建权限之前发送 post_save
。在处理信号时,您需要考虑到这一点。
@receiver(post_save, sender=User)
def user_post_save(sender, **kwargs):
"""
Create a Profile instance for all newly created User instances. We only
run on user creation to avoid having to check for existence on each call
to User.save.
"""
user, created = kwargs["instance"], kwargs["created"]
if created and user.username != settings.ANONYMOUS_USER_NAME:
from profiles.models import Profile
profile = Profile.objects.create(pk=user.pk, user=user, creator=user)
assign_perm("change_user", user, user)
assign_perm("change_profile", user, profile)
检查对象权限
一旦我们分配了一些权限,我们就可以详细了解如何验证用户或组的权限。
标准方式
通常为了检查 Joe
是否被允许更改 Site
对象,我们在 User
实例上调用 has_perm
方法:
>>> from guardian.shortcuts import assign_perm
>>> assign_perm('sites.change_site', joe, site)
<UserObjectPermission: example.com | joe | change_site>
>>> joe = User.objects.get(username='joe')
>>> joe.has_perm('sites.change_site', site)
True
内部视图
除了标准的 has_perm
方法外,django-guardian
还提供了一些有用的对象权限检查助手。
1、get_perms
>>> from guardian.shortcuts import get_perms
>>>
>>> joe = User.objects.get(username='joe')
>>> site = Site.objects.get_current()
>>>
>>> 'change_site' in get_perms(joe, site)
True
使用标准的 has_perm
方法可能更好。但对于 Group
实例来说,它并不那么容易,get_perms
在这里可能很方便,因为它接受 User
和 Group
实例。如果我们需要做更多的工作,我们可以使用下一节中描述的较低级别的 ObjectPermissionChecker
类。
2、get_objects_for_user
有时需要根据特定用户、对象类型和提供的权限提取对象列表。例如,假设在具有自定义 view_project
权限的项目应用程序中有一个项目模型。我们想向我们的用户展示他们实际可以查看的项目。这可以使用 get_objects_for_user
轻松实现:
from django.shortcuts import render
from django.template import RequestContext
from projects.models import Project
from guardian.shortcuts import get_objects_for_user
def user_dashboard(request, template_name='projects/dashboard.html'):
projects = get_objects_for_user(request.user, 'projects.view_project')
return render(request, template_name, {'projects': projects},
RequestContext(request))
ObjectPermissionChecker
在 django-guardian
的核心模块中,有一个 guardian.core.ObjectPermissionChecker
检查用户/组对特定对象的权限。它缓存结果,因此它可以在我们多次检查权限的部分代码中使用。
>>> joe = User.objects.get(username='joe')
>>> site = Site.objects.get_current()
>>> from guardian.core import ObjectPermissionChecker
>>> checker = ObjectPermissionChecker(joe) # we can pass user or group
>>> checker.has_perm('change_site', site)
True
>>> checker.has_perm('add_site', site) # no additional query made
False
>>> checker.get_perms(site)
[u'change_site']
使用装饰器
标准的 permission_required
装饰器不允许检查对象权限。 django-guardian
附带了两个装饰器,这可能有助于简单的对象权限检查,但请记住,这些装饰器会在调用装饰视图之前访问数据库——这意味着如果在一个视图中进行了类似的查找,那么很可能是一个(或多个,取决于查找)额外的数据库查询会发生。
假设我们将“group_name
”参数传递给我们的视图函数,该函数返回表单以编辑组。此外,如果检查失败,我们希望返回 403
代码。这可以使用 permission_required_or_403
装饰器简单地实现:
>>> joe = User.objects.get(username='joe')
>>> foobars = Group.objects.create(name='foobars')
>>>
>>> from guardian.decorators import permission_required_or_403
>>> from django.http import HttpResponse
>>>
>>> @permission_required_or_403('auth.change_group',
>>> (Group, 'name', 'group_name'))
>>> def edit_group(request, group_name):
>>> return HttpResponse('some form')
>>>
>>> from django.http import HttpRequest
>>> request = HttpRequest()
>>> request.user = joe
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponseForbidden object at 0x102b43dd0>
>>>
>>> joe.groups.add(foobars)
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponseForbidden object at 0x102b43e50>
>>>
>>> from guardian.shortcuts import assign_perm
>>> assign_perm('auth.change_group', joe, foobars)
<UserObjectPermission: foobars | joe | change_group>
>>>
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponse object at 0x102b8c8d0>
>>> # Note that we now get normal HttpResponse, not forbidden
内部模板
django-guardian
带有特殊的模板标签 guardian.templatetags.guardian_tags.get_obj_perms()
可以存储给定用户/组和实例对的对象权限。为了使用它,我们需要将以下内容放入模板中:
{% load guardian_tags %}
删除对象权限
删除对象权限与分配权限一样简单。我们将使用 guardian.shortcuts.remove_perm()
函数代替 guardian.shortcuts.assign_perm()
(它接受相同的参数)。
>>> from guardian.shortcuts import remove_perm
>>> remove_perm('change_site', joe, site)
>>> joe = User.objects.get(username='joe')
>>> joe.has_perm('change_site', site)
False
Admin整合
Django
带有出色且广泛使用的管理应用程序。基本上,它为 Django
应用程序提供内容管理。有权访问管理面板的用户可以管理系统提供的用户、组、权限和其他数据。
django-guardian
为 Django
的管理应用程序提供了简单的对象权限管理集成。
from django.db import models
class Post(models.Model):
title = models.CharField('title', max_length=64)
slug = models.SlugField(max_length=64)
content = models.TextField('content')
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
permissions = (
('hide_post', 'Can hide post'),
)
get_latest_by = 'created_at'
def __str__(self):
return self.title
def get_absolute_url(self):
return {'post_slug': self.slug}
我们想在管理应用程序中注册 Post
模型。通常,我们会在应用程序的 admin.py
文件中按如下方式执行此操作:
from django.contrib import admin
from posts.models import Post
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_display = ('title', 'slug', 'created_at')
search_fields = ('title', 'content')
ordering = ('-created_at',)
date_hierarchy = 'created_at'
admin.site.register(Post, PostAdmin)
如果我们想为 Post
模型添加对象权限管理,我们需要将 PostAdmin
基类更改为 GuardedModelAdmin
。我们的代码可能如下所示:
from django.contrib import admin
from posts.models import Post
from guardian.admin import GuardedModelAdmin
class PostAdmin(GuardedModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_display = ('title', 'slug', 'created_at')
search_fields = ('title', 'content')
ordering = ('-created_at',)
date_hierarchy = 'created_at'
admin.site.register(Post, PostAdmin)
自定义用户模型
Django
具有自定义默认auth.user
模型的功能 – 通过摘要抽象器或定义自己的类。这可能非常强大,但是必须谨慎完成。基本上,如果我们为Auther.group
(并给出反向关联姓名组),我们应该很好。
默认情况下,Django-Guardian
Monkey
修补了用户模型以添加一些必要的功能。如果将Guardian
导入到模型中,则可能会导致错误。自定义用户模型所处的同一应用程序的py
。
为了解决此问题,建议您在您的自定义用户模型中添加设置guardian_monkey_patch = false
。
匿名用户创建
也可以覆盖创建匿名用户实例的默认行为。例如,让我们想象我们的用户模型如下:
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
real_username = models.CharField(max_length=120, unique=True)
birth_date = models.DateField() # field without default value
USERNAME_FIELD = 'real_username'
为了覆盖创建匿名实例的方式,我们需要使Guardian_get_init_anonymous_user
指向我们的自定义实现。例如,让我们定义我们的初始功能:
import datetime
def get_anonymous_user_instance(User):
return User(real_username='Anonymous', birth_date=datetime.date(1970, 1, 1))
# myapp/models.py
GUARDIAN_GET_INIT_ANONYMOUS_USER = 'myapp.models.get_anonymous_user_instance'