可以Django的监护人和Django的规则一起使用吗? [英] Can django-guardian and django-rules be used together?

查看:278
本文介绍了可以Django的监护人和Django的规则一起使用吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望能够创建一个使用 <$每个对象的权限C $ C> Django的监护人

I'd like to be able to create per-object permissions using django-guardian.

不过,我想补充围绕这些权限的逻辑层。例如,如果某人有 edit_book 图书,那么他们的权限编辑页面<许可/ code>在这本书应该是隐含的。该 规则 包似乎理想。

But I'd like to add a layer of logic surrounding these permissions. For example if someone has edit_book permission on a Book, then their permission to edit Pages in that book should be implicit. The rules package seems ideal.

推荐答案

出现以下工作:

import rules
import guardian

@rules.predicate
def is_page_book_editor(user, page):
    return user.has_perm('books.edit_book', page.book)

@rules.predicate
def is_page_editor(user, page):
    return user.has_perm('pages.edit_page', page)

rules.add_perm('pages.can_edit_page', is_page_book_editor | is_page_editor)

然后检查:

joe.has_perm('pages.can_edit_page', page34)

或者

@permission_required('pages.can_edit_page', fn=objectgetter(Page, 'page_id'))
def post_update(request, page_id):
    # ...

通过认证后端定义的:

AUTHENTICATION_BACKENDS = (
    'rules.permissions.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
    'guardian.backends.ObjectPermissionBackend',
)

进口:

from django.contrib.auth.models import User
import rules
import guardian
from guardian.shortcuts import assign_perm
from myapp.models import Book, Page

测试:

joe = User.objects.create(username='joe', email='joe@example.com')
page23 = Page.objects.filter(id=123)
assign_perm('edit_page', joe, page23)
joe.has_perm('edit_page', page23)
is_page_editor(joe, page23)  # returns True
joe.has_perm('can_edit_page', i)  # returns True

rules.remove_perm('can_edit_page')
rules.add_perm('can_edit_page', is_page_book_editor & is_page_editor)
joe.has_perm('can_edit_page', i)  # returns False


与这方面的一个问题是,每个的规则进行检查时,每predicate使得对数据库的呼叫。以下补充缓存,以便只有一个为每个规则检查查询:


A problem with this is that each time a rule is checked, each predicate makes a call to the database. The following adds caching so that there is only one query for each rule check:

@rules.predicate
def is_page_book_viewer(user, instance):
    if is_page_book_viewer.context.get('user_perms') is None:
        is_page_book_viewer.context['user_perms'] = guardian.shortcuts.get_perms(user, page.book)
    return 'view_book' in is_page_book_viewer.context.get('user_perms')

@rules.predicate(bind=True)
def is_page_viewer(self, user, instance):
    if self.context.get('user_perms') is None:
        self.context['user_perms'] = guardian.shortcuts.get_perms(user, instance)
    return 'view_page' in self.context.get('user_perms')

(我绑定在第二个例子中,并使用,而这等同于使用predicate名。)

(I bind in the second example and use self, but this is identical to using the predicate name.)

当你在做复杂的,复合的权限,它可能是明智的<一个href=\"https://django-guardian.readthedocs.io/en/stable/userguide/performance.html#direct-foreign-keys\"相对=nofollow>与以假乱真取代Django的监护人的通用外键可以优化和数据库,像这样索引:

As you're doing complex, composite permissions, it is probably wise to replace django-guardian's generic foreign keys with real ones that can be optimized and indexed by the database like so:

class PageUserObjectPermission(UserObjectPermissionBase):
    content_object = models.ForeignKey(Page)

class PageGroupObjectPermission(GroupObjectPermissionBase):
    content_object = models.ForeignKey(Page)

class BookUserObjectPermission(UserObjectPermissionBase):
    content_object = models.ForeignKey(Book)

class BookGroupObjectPermission(GroupObjectPermissionBase):
    content_object = models.ForeignKey(Book)


有一个bug。我们在缓存图书权限,可以在同一个地方 - 我们需要区分并单独缓存这些。此外,我们封装反复code到它自己的方法。最后,让我们给的get()默认,以确保我们不会再查询用户的权限时,他们有


There is a bug. We're caching permissions on Page and Book in the same place - we need to distinguish and cache these separately. Also, let's encapsulate the repeated code into its own method. Finally, let's give get() a default to make sure we don't re-query a user's permissions when they have None.

def cache_permissions(predicate, user, instance):
    """
    Cache all permissions this user has on this instance, for potential reuse by other predicates in this rule check.
    """
    key = 'user_%s_perms_%s_%s' % (user.pk, type(instance).__name__, instance.pk)
    if predicate.context.get(key, -1) == -1:
        predicate.context[key] = guardian.shortcuts.get_perms(user, instance)
    return predicate.context[key]

这样的对象权限将会分别缓存。 (在包括用户ID是不必要的,因为任何规则将只检查一个用户,而是多了几分面向未来的。)

This way object permissions will be cached separately. (Including user id in key is unnecessary as any rule will only check one user, but is a little more future-proof.)

然后我们就可以定义我们的predicates如下:

Then we can define our predicates as follows:

@rules.predicate(bind=True)
def is_page_book_viewer(self, user, instance: Page):
    return 'view_book' in cache_permissions(self, user, instance.book)


规则的一个限制是权限检查必须基于用户单独完成,但我们往往要得到所有对象的用户拥有一个给定的权限。例如,要获得所有页面的列表,用户具有编辑权限,我需要反复调用 [P对p的Pages.objects.all()如果usr.has_perm('can_edit_page',P) ,而不是 usr.has_perm('can_edit_page')返回一个查询中所有允许的对象。


One limitation of rules is permission checks have to be done individually based on user, but we often have to get all objects a user has a given permission on. For example to get a list of all pages the user has edit permissions on I need to repeatedly call [p for p in Pages.objects.all() if usr.has_perm('can_edit_page', p)], rather than usr.has_perm('can_edit_page') returning all permitted objects in one query.

我们不能完全解决此限制,但在这里我们并不需要检查每一个对象在列表中,我们可以通过减少查询的数量接下来和懒发电机基于协同程序,查询集。在上面的例子中,我们可以使用(...),而不是 [...] 如果我们可能不会去到列表的末尾,下一个(...)如果我们只需要检查是否的任何的列表中的对象具有的权限。 收益将是正常循环code等效,如下图所示。

We can't fully address this limitation, but where we don't need to check every object in a list, we can reduce the number of queries using next and lazy generator coroutine-based querysets. In the above example we could use (...) rather than [...] if we may not go to the end of the list, and next(...) if we only need to check whether any object in the list has the permission. break or return would be the equivalents in normal looping code, as below.

我在那里有一个模型具有自联接层次结构中的情况,我只是需要一个模型的后人知道的任何的具有的权限。在code必须递归查询与连续节点的后代表。但只要我们找到具有权限的对象,我们不需要进一步查询任何。我已经做如下这一点。 (请注意我感兴趣的是是否的任何的有对象的权限,我指定非通用密钥。如果你正在检查的权限,你可以使用一个特定的用户 user.has_perm('perm_name',OBJ)来使用您的规则。)

I have a situation where a Model has a self-join hierarchy, and I just need to know if any of a model's descendants has a permission. The code must recursively query the table with successive nodes' descendants. But as soon as we find an object with the permission, we needn't query any further. I have done this as follows. (Note I am interested in whether anyone has the permission on an object, and I've specified non-generic keys. If you're checking the permission for a specific user you can use user.has_perm('perm_name', obj) to use your rules.)

class Foo(models.Model):
    parent = models.ForeignKey('Foo', blank=True, null=True)

    def descendants(self):
        """
        When callers don't need the complete list (eg, checking if any dependent is 
        viewable by any user), we run fewer queries by only going into the dependent 
        hierarchy as much as necessary.
        """
        immediate_descendants = Foo.objects.filter(parent=self)
        for x in immediate_descendants:
            yield x
        for x in immediate_descendants:
            for y in x.descendants():
                yield y

    def obj_or_descendant_has_perm(self, perm_code):
        perm_id = Permission.objects.get(codename=perm_code).id

        if FooUserObjectPermission.objects.filter(permission_id=perm_id,
                                                  content_object=self).exists()
            return True
        if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
                                                   content_object=self).exists()
            return True

        for o in self.descendants():
            if FooUserObjectPermission.objects.filter(permission_id=perm_id,
                                                      content_object=self).exists()
                return True
            if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
                                                       content_object=self).exists()
                return True

        return False

如果你有一个自联接就是这种简单,请 树胡 建模层次的更有效的方法(物化路径,嵌套集合或邻接表)。在我的情况下,自连接是通过其他表,所以这是不可能的。

If you have a self-join that is this simple, check out treebeard for more efficient ways of modelling hierarchies (materialized paths, nested sets or adjacency lists). In my case the self-join was via other tables so this wasn't possible.

我走了一步,允许组由后代返回的查询集选择:

I went a step further and permitted group selects by returning querysets from descendants:

class Foo(models.Model):
    parent = models.ForeignKey('Foo', blank=True, null=True)

    def descendants(self):
        """
        When callers don't need the complete list (eg, checking if any dependent is 
        viewable by any user), we run fewer queries by only going into the dependent 
        hierarchy as much as necessary. Returns a generator of querysets of Foo objects.
        """
        immediate_descendants = Foo.objects.filter(parent=self)
        yield immediate_descendants
        for x in immediate_descendants:
            for y in x.descendants():
                yield y

    def obj_or_descendant_has_perm(self, perm_code):
        perm_id = Permission.objects.get(codename=perm_code).id

        if FooUserObjectPermission.objects.filter(permission_id=perm_id,
                                                  content_object=self).exists()
            return True
        if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
                                                   content_object=self).exists()
            return True

        for gen in self.descendants():
            if FooUserObjectPermission.objects.filter(permission_id=perm_id,
                                                      content_object__in=gen).exists()
                return True
            if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
                                                       content_object__in=gen).exists()
                return True

        return False

这篇关于可以Django的监护人和Django的规则一起使用吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆