项目描述

Rules是一个很小但功能强大的应用程序,为Django提供对象级权限,而不需要数据库。它的核心是构建基于规则的系统的通用框架,类似于决策树。它还可以在其他上下文和框架中用作独立的库。

https://travis-ci.org/dfunckt/django-rules.svg?branch=master https://coveralls.io/repos/dfunckt/django-rules/badge.svg https://img.shields.io/pypi/v/rules.svg https://img.shields.io/pypi/pyversions/rules.svg

特点

rules has got you covered. rules is:

  • 文档、测试、可靠且易于使用。.
  • 灵活. 通过装饰可调用对象来构建复杂的谓词图 . Predicates 可以是任何类型的callable对象 – 简单函数、lambda、方法、可调用类对象、部分函数、修饰函数
  • A good Django citizen. 与Django视图、模板和管理员无缝集成,以测试对象级权限。.
  • 高效和聪明。不必为了弄清楚 “约翰是否真的写了那本书” 而乱弄数据库。
  • 简单。深入了解代码。你需要10分钟来弄清楚它是如何工作的
  • 强大。规则具有高级功能,例如调用上下文和存储任意数据、跳过特定条件下的谓词评估、记录评估的谓词等等!

安装需求

rules需要python 2.7/3.4或更高版本。它可以选择与Django集成,在这种情况下需要Django 1.11或更高版本。.

Note: At any given moment in time, rules will maintain support for all currently supported Django versions, while dropping support for those versions that reached end-of-life in minor releases. See the Supported Versions section on Django Project website for the current state and timeline.

从1.x升级

  • 支持Python 2.6 / 3.3, 不支持对Django==1.11之前版本的升级.
  • Predicate类的SkipPredicate 异常和 skip() 方法, (曾用于表示谓词是否跳过) 已被移除。 你可以从谓词中返回None来实现.
  • 替换rule的谓词的APIs被重命名且行为被更改. replace_rule 和 replace_perm 方法和 RuleSet的replace_rule方法已分别被重新命名为 set_rule,set_perm 和RuleSet.set_perm. 如果给定name的rule 不存在旧行为是抛出 KeyError 异常. 自2.0版以来,这一点已经改变,您可以安全地使用set_*来设置rule的谓词,而无需确保规则首先存在。

怎样安装

使用pip:

$ pip install rules

手动安装:

$ git clone https://github.com/dfunckt/django-rules.git
$ cd django-rules
$ python setup.py install

执行测试:

$ ./runtests.sh

你可能需要阅读 Best practices 以获得有关如何使用 rules的一般性建议。

在Django中配置rules

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

使用 Rules

rules基于这样的思想: 你维护一个RuleSet类对象(类似dict),相当于一个映射表,由字符串标识到Predicate类对象的映射

创建predicates

暂时忽略 rule sets,先定义一个 Predicate对象. 最简单的方法是通过 @predicate 装饰器:

>>> @rules.predicate
>>> def is_book_author(user, book):
...     return book.author == user
...
>>> is_book_author
<Predicate:is_book_author object at 0x10eeaa490>

如果书的作者是给定的user,This predicate 将返回 True ,否则返回 False.

Predicates 能被创建 通过 接收任意数量的位置参数的可调用对象:

  • fn(obj, target)
  • fn(obj)
  • fn()

这是它们的一般形式。如果从Django的授权角度来看,等效签名为

  • fn(user, obj)
  • fn(user)
  • fn()

Predicates可以对给定的参数执行任何操作,但如果它们检查的条件为真,则必须始终返回真,否则返回假。规则附带了几个预定义的谓词,您稍后可能会在 API Reference,中了解到这些谓词,这些谓词在处理authorization in Django.时非常有用。

设置 rules

假设我们想让作者编辑或删除他们的书,而不是其他作者写的书。所以,本质上,决定一个作者是否可以编辑或删除一本给定的书的是他们是否是作者。

在rules中,这些需求被当做rules。一个rule是唯一标识符(例如“can edit”)到predicate的映射。许多规则被组合成一个rule set。rules有两个预定义的rule set::

  • 其中一个默认的rule set 保存共享的 rules. rules.add_rule('name',  is_xxx)
  • 另外一个rule set保存作为权限在Django上下文中生效的rules  rules.add_perm(...)

So, 让我们定义第一组rules, 添加他们到共享的rule set. 我们可以使用之前定义过的 is_book_author谓词对象

>>> rules.add_rule('can_edit_book', is_book_author)
>>> rules.add_rule('can_delete_book', is_book_author)

假设我们有一些数据,我们现在可以测试我们的规则:

>>> from django.contrib.auth.models import User
>>> from books.models import Book
>>> book1 = Book.objects.get(isbn='978-1-4302-1936-1')
>>> book1.author
<User: Tom>
>>> author_tom = User.objects.get(username='Tom')
>>> rules.test_rule('can_edit_book', author_tom, book1)
True
>>> rules.test_rule('can_delete_book', author_tom, book1)
True

Nice… but not awesome(棒).

组合predicates

Predicates本身并没有那么有用——比任何其他函数都没那么有用。然而,Predicates可以使用二元运算符组合以创建更复杂的谓词。谓词支持以下运算符:

  • P1 & P2: 且,如果P1,P2都为True则返回值为True的新predicate,否则返回 False. 如果 P1 为 False, P2 不会进行判断.
  • P1 | P2: 或,如果P1,P2有一个为True,则返回值为True的新 predicate , 否则返回False. 如果P1 为True, P2不会进行判断.
  • P1 ^ P2:异或,如果P1,P2一个True一个False,则返回值为True的新 predicate, 否则返回False.
  • ~P: 非,返回与原predicate相反结果的predicate.

假设允许用户编辑一本给定的书的要求是他们要么是书的作者,要么是“编辑”组的成员。允许用户删除一本书仍然应该由用户是否是书的作者来决定。

使用易于实现的规则。我们必须定义另一个谓词,如果给定的用户是“editors”组的成员,则返回true,否则返回false。内置的is_group_member 工厂将派上用场:

>>> is_editor = rules.is_group_member('editors')  # 是不是某个角色
>>> is_editor
<Predicate:is_group_member:editors object at 0x10eee1350>

我们可以将它与 is_book_author谓词进行合并,来创建一个新谓词来检查他们其中之一是否成立:

>>> is_book_author_or_editor = is_book_author | is_editor
>>> is_book_author_or_editor
<Predicate:(is_book_author | is_group_member:editors) object at 0x10eee1390>

我们现在可以更新 can_edit_book 规则:

>>> rules.set_rule('can_edit_book', is_book_author_or_editor)
>>> rules.test_rule('can_edit_book', adrian, guidetodjango)
True
>>> rules.test_rule('can_delete_book', adrian, guidetodjango)
True

 让我们测试一下,如果换其他用户会怎么样:

>>> martin = User.objects.get(username='martin')
>>> list(martin.groups.values_list('name', flat=True))
['editors']
>>> rules.test_rule('can_edit_book', martin, guidetodjango)
True
>>> rules.test_rule('can_delete_book', martin, guidetodjango)
False

很棒.

到目前为止,我们只使用了底层的通用框架来定义和测试规则。这一层根本不特定于Django;它可以在任何上下文中使用。实际上,整个应用程序中没有任何与django相关的导入(rules.templateTags模块除外)。但是,规则可以与Django紧密集成以提供授权。So far, we’ve only used the underlying, generic framework for defining and testing rules. This layer is not at all specific to Django; it may be used in any context. There’s actually no import of anything Django-related in the whole app (except in the rules.templatetags module). rules however can integrate tightly with Django to provide authorization.

Using Rules with Django

rules能在Django中提供对象级别的权限控制,它附带了一个授权后端和几个用于模板中模板tags。

rules is able to provide object-level permissions in Django. It comes with an authorization backend and a couple template tags for use in your templates.

Permissions

在rules中,permissions是特定类型的rules,你仍可通过创建或合并谓词来定义他们,那些规则必须添加到权限指定的rule set以便rules认证后端能提取到

In rules, permissions are a specialised type of rules. You still define rules by creating and combining predicates. These rules however, must be added to a permissions-specific rule set that comes with rules so that they can be picked up by the rules authorization backend.

Creating permissions

Django中命名权限的约定是app_label.action_object,我们希望遵守这个约定。让我们为书籍添加规则。更改书籍和书籍。删除书籍权限:

The convention for naming permissions in Django is app_label.action_object, and we like to adhere to that. Let’s add rules for the books.change_book and books.delete_book permissions:

>>> rules.add_perm('books.change_book', is_book_author | is_editor)
>>> rules.add_perm('books.delete_book', is_book_author)

看到API的区别了吗?add_perm 将添加到permissions-specific rule set,而add_rule 将添加到 default shared rule set。但是,重要的是要知道这两个规则集是分开的,这意味着在其中一个添加的rule不会在另一个里可用。

See the difference in the API? add_perm adds to a permissions-specific rule set, whereas add_rule adds to a default shared rule set. It’s important to know however, that these two rule sets are separate, meaning that adding a rule in one does not make it available to the other.

Checking for permission

让我们检查一下adrian 用户对guidetodjango 书籍的修改权限

Let’s go ahead and check whether adrian has change permission to the guidetodjango book:

>>> adrian.has_perm('books.change_book', guidetodjango)
False

当您调用User.has_perm方法时,django会询问每个在settings.authentication-backends里的认证后端,用户是否具有对象的给定权限。当查询对象权限时,Django的默认认证后端始终返回false。rules带有一个认证后端,它可以通过查看 permissions-specific rule set来提供对象级权限。

让我们在设置中添加rules认证后端:

When you call the User.has_perm method, Django asks each backend insettings.AUTHENTICATION_BACKENDS whether a user has the given permission for the object. When queried for object permissions, Django’s default authentication backend always returns Falserules comes with an authorization backend, that is able to provide object-level permissions by looking into the permissions-specific rule set.

Let’s add the rules authorization backend in settings:

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

Now, checking again gives adrian the required permissions:

现在再次检查adrian 的对象权限

>>> adrian.has_perm('books.change_book', guidetodjango)
True
>>> adrian.has_perm('books.delete_book', guidetodjango)
True
>>> martin.has_perm('books.change_book', guidetodjango)
True
>>> martin.has_perm('books.delete_book', guidetodjango)
False

Permissions in models

注意:本节中描述的特性仅适用于Python3+。

NOTE: The features described in this section work on Python 3+ only.

对于一个model有一组权限是很常见的,比如Django用它的默认模型权限(如添加、更改等)提供的权限。当使用rules作为权限检查后端时,可以使用新的meta选项以类似的方式声明任何model的对象级权限。

It is common to have a set of permissions for a model, like what Django offers with its default model permissions (such as addchange etc.). When using rules as the permission checking backend, you can declare object-level permissions for any model in a similar way, using a new Meta option.

首先,您需要将model的基类和元类切换到rules.contrib.models中提供的稍微扩展的版本。有几个classes和mixins可以使用,这取决于您是否已经为您的模型使用了自定义的基和/或元类。扩展非常小,除了使其注册权限之外,不会以任何方式影响model的行为。

First, you need to switch your model’s base and metaclass to the slightly extended versions provided in rules.contrib.models. There are several classes and mixins you can use, depending on whether you’re already using a custom base and/or metaclass for your models or not. The extensions are very slim and don’t affect the models’ behavior in any way other than making it register permissions.

如果您正使用django.db.models.model作为您的model的基类,只需切换到RulesModel,就可以很好地使用了。

如果您已经有了一个向model添加公共功能的自定义基类,请将RulesModelMixin添加到它继承的类中,并将RulesModelBase设置为它的元类,如下所示:

  • If you’re using the stock django.db.models.Model as base for your models, simply switch over to RulesModel and you’re good to go.

  • If you already have a custom base class adding common functionality to your models, add RulesModelMixin to the classes it inherits from and set RulesModelBase as its metaclass, like so:

    from django.db.models import Model
    from rules.contrib.models import RulesModelBase, RulesModelMixin
    
    class MyModel(RulesModelMixin, Model, metaclass=RulesModelBase):
        ...
    
  • If you’re using a custom metaclass for your models, you’ll already know how to make it inherit from RulesModelBaseMixin yourself.

Then, create your models like so, assuming you’re using RulesModel as base directly:

如果您正在为model使用自定义元类,那么您就已经知道如何让它从RulesModelBaseMixin继承。

然后,像这样创建模型,假设您直接使用RulesModel作为基类:

import rules
from rules.contrib.models import RulesModel

class Book(RulesModel):
    class Meta:
        rules_permissions = {
            "add": rules.is_staff,
            "read": rules.is_authenticated,
        }

This would be equivalent to the following calls:

这相当于以下调用:

rules.add_perm("app_label.add_book", rules.is_staff)
rules.add_perm("app_label.read_book", rules.is_authenticated)

RulesModelMixin中有一些方法可以覆盖,以便自定义如何注册模型的权限。如果需要,请参阅文档化的源代码以了解详细信息。

特别感兴趣的是RulesModelMixin的get_perm 类方法,它可以用于将权限类型转换为相应的完整权限名称。如果需要以编程方式查询给定模型的某种类型的权限,这很方便:

There are methods in RulesModelMixin that you can overwrite in order to customize how a model’s permissions are registered. See the documented source code for details if you need this.

Of special interest is the get_perm classmethod of RulesModelMixin, which can be used to convert a permission type to the corresponding full permission name. If you need to query for some type of permission on a given model programmatically, this is handy:

if user.has_perm(Book.get_perm("read")):
    ...

Permissions in views

rules附带了一组装饰器来帮助您在视图中强制授权。

rules comes with a set of view decorators to help you enforce authorization in your views.

Using the function-based view decorator

对于基于函数的视图,可以使用permission_required装饰器:

For function-based views you can use the permission_required decorator:

from django.shortcuts import get_object_or_404
from rules.contrib.views import permission_required
from posts.models import Post

def get_post_by_pk(request, post_id):
    return get_object_or_404(Post, pk=post_id)

@permission_required('posts.change_post', fn=get_post_by_pk)
def post_update(request, post_id):
    # ...

用法是直截了当的,但是在上面的示例中有一件事是突出的,这就是get_post_by_pk 函数。此函数给定当前请求和传递给视图的所有参数,负责获取和返回要检查权限的对象,POST对象中的主键等于给定的post_id。这个特定的用例是非常常见的,所以为了节省一些输入,rules附带了一个通用的助手函数,您可以使用它来声明性地完成这项工作。下面的例子相当于上面的例子:

Usage is straight-forward, but there’s one thing in the example above that stands out and this is the get_post_by_pk function. This function, given the current request and all arguments passed to the view, is responsible for fetching and returning the object to check permissions against – i.e. the Post instance with PK equal to the given post_id in the example. This specific use-case is quite common so, to save you some typing, rules comes with a generic helper function that you can use to do this declaratively. The example below is equivalent to the one above:

from rules.contrib.views import permission_required, objectgetter
from posts.models import Post

@permission_required('posts.change_post', fn=objectgetter(Post, 'post_id'))
def post_update(request, post_id):
    # ...

有关decorator和helper函数的详细信息,请参阅rules.contrib.views模块。

For more information on the decorator and helper function, refer to the rules.contrib.views module.

Using the class-based view mixin

Django包含一组访问混合函数,您可以在基于类的视图中使用它们来强制授权。rules 扩展了这个框架,通过mixin提供对象级权限,PermissionRequiredMixin。

下面的示例将针对视图的get_object 方法返回的实例自动测试权限:

Django includes a set of access mixins that you can use in your class-based views to enforce authorization. rules extends this framework to provide object-level permissions via a mixin, PermissionRequiredMixin.

The following example will automatically test for permission against the instance returned by the view’s get_object method:

from django.views.generic.edit import UpdateView
from rules.contrib.views import PermissionRequiredMixin
from posts.models import Post

class PostUpdate(PermissionRequiredMixin, UpdateView):
    model = Post
    permission_required = 'posts.change_post'

您可以通过覆盖get_object 或get_permission_object来自定义对象。

有关更多信息,请参阅django文档和rules.contrib.views模块。

You can customise the object either by overriding get_object or get_permission_object.

For more information refer to the Django documentation and the rules.contrib.views module.

Checking permission automatically based on view type

如果您使用rules.contrib.models提供的机制来注册模型的权限(如Permissions in models中所述),那么对于基于类的视图,还有另一种方便的混合方法。

rules.contrib.views.AutoPermissionRequiredMixin可以识别使用的视图类型,并自动检查相应的权限。

如果应用标签为"posts",则此示例视图将在不进行任何进一步配置的情况下自动检查 "posts.change_post"权限:

If you use the mechanisms provided by rules.contrib.models to register permissions for your models as described in Permissions in models, there’s another convenient mixin for class-based views available for you.

rules.contrib.views.AutoPermissionRequiredMixin can recognize the type of view it’s used with and check for the corresponding permission automatically.

This example view would, without any further configuration, automatically check for the "posts.change_post" permission, given that the app label is "posts":

from django.views.generic import UpdateView
from rules.contrib.views import AutoPermissionRequiredMixin
from posts.models import Post

class UpdatePostView(AutoPermissionRequiredMixin, UpdateView):
    model = Post

默认情况下,django.views.generic中的通用CRUD视图映射到本机django权限类型(add、change、delete和view)。但是,当子类化autopermissionRequiredMixin时,可以扩展、更改或替换预定义的映射。有关如何正确执行此操作的详细信息,请参阅完全文档化的源代码。

By default, the generic CRUD views from django.views.generic are mapped to the native Django permission types (addchangedelete and view). However, the pre-defined mappings can be extended, changed or replaced altogether when subclassing AutoPermissionRequiredMixin. See the fully documented source code for details on how to do that properly.

Permissions and rules in templates

规则带有两个模板标记,允许您测试模板中的rules和permissions。

rules comes with two template tags to allow you to test for rules and permissions in templates.

Add rules to your INSTALLED_APPS:

INSTALLED_APPS = (
    # ...
    'rules',
)

Then, in your template:

{% load rules %}

{% has_perm 'books.change_book' author book as can_edit_book %}
{% if can_edit_book %}
    ...
{% endif %}

{% test_rule 'has_super_feature' user as has_super_feature %}
{% if has_super_feature %}
    ...
{% endif %}

Permissions in the Admin

如果您在Django中设置了要与permissions一起使用的rules,使用rules 在Admin中认证add/change/delete。Admin要求四种不同的权限:

If you’ve setup rules to be used with permissions in Django, you’re almost set to also use rules to authorize any add/change/delete actions in the Admin. The Admin asks for four different permissions, depending on action:

  • <app_label>.add_<modelname>
  • <app_label>.view_<modelname>
  • <app_label>.change_<modelname>
  • <app_label>.delete_<modelname>
  • <app_label>

注意:view_permission在Django v2.1中是新的。

前四个很明显。第五个是在管理员的“仪表板”中显示应用程序所需的权限。以下是我们虚构的 books 应用程序的一些规则示例:

Note: view permission is new in Django v2.1.

The first four are obvious. The fifth is the required permission for an app to be displayed in the Admin’s “dashboard”. Here’s some rules for our imaginary books app as an example:

>>> rules.add_perm('books', rules.always_allow)
>>> rules.add_perm('books.add_book', is_staff)
>>> rules.add_perm('books.view_book', is_staff | has_secret_access_code)
>>> rules.add_perm('books.change_book', is_staff)
>>> rules.add_perm('books.delete_book', is_staff)

django-admin不支持对象权限,从这个意义上说,它永远不会请求对对象执行操作的权限,只允许用户对模型的(任何)实例执行操作。

如果要告诉Django用户是否对特定对象具有权限,则必须重写模型的ModelAdmin的以下方法:

Django Admin does not support object-permissions, in the sense that it will never ask for permission to perform an action on an object, only whether a user is allowed to act on (any) instances of a model.

If you’d like to tell Django whether a user has permissions on a specific object, you’d have to override the following methods of a model’s ModelAdmin:

  • has_view_permission(user, obj=None)
  • has_change_permission(user, obj=None)
  • has_delete_permission(user, obj=None)

rules带有一个自定义的ModelAdmin 子类rules.contrib.admin.ObjectPermissionsModelAdmin,它重写这些方法以将编辑的模型实例传递到授权后端,从而在管理中启用每个对象的权限:

rules comes with a custom ModelAdmin subclass,rules.contrib.admin.ObjectPermissionsModelAdmin, that overrides these methods to pass on the edited model instance to the authorization backends, thus enabling permissions per object in the Admin:

# books/admin.py
from django.contrib import admin
from rules.contrib.admin import ObjectPermissionsModelAdmin
from .models import Book

class BookAdmin(ObjectPermissionsModelAdmin):
    pass

admin.site.register(Book, BookAdmin)

Now this allows you to specify permissions like this:

>>> rules.add_perm('books', rules.always_allow)
>>> rules.add_perm('books.add_book', has_author_profile)
>>> rules.add_perm('books.change_book', is_book_author_or_editor)
>>> rules.add_perm('books.delete_book', is_book_author)

为了保持向后兼容性,Django将请求查看或更改权限。为了获得最大的灵活性,规则的行为略有不同:如果并且仅当视图权限不存在规则时,规则才会请求更改权限。

To preserve backwards compatibility, Django will ask for either view or change permission. For maximum flexibility, rules behaves subtly different: rules will ask for the change permission if and only if no rule exists for the view permission.

Permissions in Django Rest Framework

Similar to rules.contrib.views.autopermission requiredmixin,there is arules.contrib.rest=uframework.autopermissions viewsetmixin for viewsets in Django rest framework.不同之处在于,这并不是从视野类型获得的许可,而是从API行动(创作、检索等)中获得的许可,而API行动力求得到许可。当然,它要求你使用规则中的混合物。contrib.modelswhen declaring models the API should operate on.

这是一个完全自动化的许可证检查的可能的模型:

Similar to rules.contrib.views.AutoPermissionRequiredMixin, there is arules.contrib.rest_framework.AutoPermissionViewSetMixin for viewsets in Django Rest Framework. The difference is that it doesn’t derive permission from the type of view but from the API action (createretrieveetc.) that’s tried to be performed. Of course, it requires you to use the mixins from rules.contrib.modelswhen declaring models the API should operate on.

Here is a possible ModelViewSet for the Post model with fully automated CRUD permission checking:

from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rules.contrib.rest_framework import AutoPermissionViewSetMixin
from posts.models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

class PostViewSet(AutoPermissionViewSetMixin, ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

默认情况下,modelviewset的CRUD操作映射到本机django权限类型(添加、更改、删除和查看)。列表操作未启用权限检查。但是,在使用(或子类化)autopermissionviewsetmixin时,可以完全扩展、更改或替换预定义的映射。然后也可以映射通过@action decorator定义的自定义API操作。有关如何正确自定义默认行为的详细信息,请参阅完整文档化的源代码。

By default, the CRUD actions of ModelViewSet are mapped to the native Django permission types (addchangedelete and view). The list action has no permission checking enabled. However, the pre-defined mappings can be extended, changed or replaced altogether when using (or subclassing)AutoPermissionViewSetMixin. Custom API actions defined via the @action decorator may then be mapped as well. See the fully documented source code for details on how to properly customize the default behaviour.

Advanced features

Custom rule sets

You may create as many rule sets as you need:

>>> features = rules.RuleSet()

And manipulate them by adding, removing, querying and testing rules:

>>> features.rule_exists('has_super_feature')
False
>>> is_special_user = rules.is_group_member('special')
>>> features.add_rule('has_super_feature', is_special_user)
>>> 'has_super_feature' in features
True
>>> features['has_super_feature']
<Predicate:is_group_member:special object at 0x10eeaa500>
>>> features.test_rule('has_super_feature', adrian)
True
>>> features.remove_rule('has_super_feature')

Note however that custom rule sets are not available in Django templates – you need to provide integration yourself.

Invocation context

新上下文是作为调用predicate.test()的结果创建的,并且仅在调用期间有效。上下文是一个简单的dict,您可以使用它来存储任意数据(例如缓存计算值、设置标志等),谓词稍后可以在链中使用它。在谓词函数内部,可以这样使用:

A new context is created as a result of invoking Predicate.test() and is only valid for the duration of the invocation. A context is a simple dict that you can use to store arbitrary data, (eg. caching computed values, setting flags, etc.), that can be used by predicates later on in the chain. Inside a predicate function it can be used like so:

>>> @predicate
... def mypred(a, b):
...     value = compute_expensive_value(a)
...     mypred.context['value'] = value
...     return True

Other predicates can later use stored values:

>>> @predicate
... def myotherpred(a, b):
...     value = myotherpred.context.get('value')
...     if value is not None:
...         return do_something_with_value(value)
...     else:
...         return do_something_without_value()

Predicate.context provides a single args attribute that contains the arguments as given to test() at the beginning of the invocation.

Binding “self”

在谓词的函数体中,可以通过其名称引用谓词实例本身,例如is_book_author。将bind=true作为关键字参数传递给谓词修饰器将允许您使用self引用谓词,这更方便。束缚自我只是句法上的糖分。事实上,以下两项是等效的:

In a predicate’s function body, you can refer to the predicate instance itself by its name, eg. is_book_author. Passing bind=True as a keyword argument to the predicate decorator will let you refer to the predicate with self, which is more convenient. Binding self is just syntactic sugar. As a matter of fact, the following two are equivalent:

>>> @predicate
... def is_book_author(user, book):
...     if is_book_author.context.args:
...         return user == book.author
...     return False

>>> @predicate(bind=True)
... def is_book_author(self, user, book):
...     if self.context.args:
...         return user == book.author
...     return False

Skipping predicates

You may skip evaluation by returning None from your predicate:

>>> @predicate(bind=True)
... def is_book_author(self, user, book):
...     if len(self.context.args) > 1:
...         return user == book.author
...     else:
...         return None

Returning None signifies that the predicate need not be evaluated, thus leaving the predicate result up to that point unchanged.

Logging predicate evaluation

可以选择将规则配置为在计算规则以帮助调试谓词时记录调试信息。消息在调试级别发送到“rules”记录器。以下dictconfigconfigures配置一个控制台记录器(如果在Django中使用规则,请将其放在项目的settings.py中):

rules can optionally be configured to log debug information as rules are evaluated to help with debugging your predicates. Messages are sent at the DEBUG level to the 'rules' logger. The following dictConfigconfigures a console logger (place this in your project’s settings.py if you’re using rules with Django):

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'rules': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

When this logger is active each individual predicate will have a log message printed when it is evaluated.

Best practices

在测试规则之前,必须用规则集注册这些规则,为此,必须导入包含规则定义的模块。

对于具有多个谓词和规则的复杂项目,在一个模块中定义所有谓词和规则可能并不实际。最好将它们分割到项目的任何子组件中。在Django上下文中,这些子组件可能是项目的应用程序。

另一方面,由于从各地导入谓词以定义规则会导致循环导入和心碎,因此最好在不同模块中进一步拆分谓词和规则。

可以选择将规则配置为应用程序中的autodiscover rules.py模块,并在启动时导入它们。要让规则这样做,只需编辑已安装的应用程序设置:

Before you can test for rules, these rules must be registered with a rule set, and for this to happen the modules containing your rule definitions must be imported.

For complex projects with several predicates and rules, it may not be practical to define all your predicates and rules inside one module. It might be best to split them among any sub-components of your project. In a Django context, these sub-components could be the apps for your project.

On the other hand, because importing predicates from all over the place in order to define rules can lead to circular imports and broken hearts, it’s best to further split predicates and rules in different modules.

rules may optionally be configured to autodiscover rules.py modules in your apps and import them at startup. To have rules do so, just edit your INSTALLED_APPS setting:

INSTALLED_APPS = (
    # replace 'rules' with:
    'rules.apps.AutodiscoverRulesConfig',
)

Note: On Python 2, you must also add the following to the top of your rules.py file, or you’ll get import errors trying to import rules itself:

from __future__ import absolute_import

API Reference

核心API可以从根规则模块访问。管理和视图的Django特定功能可从rules.contrib获得。

The core APIs are accessible from the root rules module. Django-specific functionality for the Admin and views is available from rules.contrib.

Class rules.Predicate

You create Predicate instances by passing in a callable:

>>> def is_book_author(user, book):
...     return book.author == user
...
>>> pred = Predicate(is_book_author)
>>> pred
<Predicate:is_book_author object at 0x10eeaa490>

You may optionally provide a different name for the predicate that is used when inspecting it:

>>> pred = Predicate(is_book_author, name='another_name')
>>> pred
<Predicate:another_name object at 0x10eeaa490>

Also, you may optionally provide bind=True in order to be able to access the predicate instance with self:

>>> def is_book_author(self, user, book):
...     if self.context.args:
...         return user == book.author
...     return False
...
>>> pred = Predicate(is_book_author, bind=True)
>>> pred
<Predicate:is_book_author object at 0x10eeaa490>

Instance methods

test(obj=None, target=None)
Returns the result of calling the passed in callable with zero, one or two positional arguments, depending on how many it accepts.

Class rules.RuleSet

RuleSet extends Python’s built-in dict type. Therefore, you may create and use a rule set any way you’d use a dict.

Instance methods

add_rule(name, predicate)
Adds a predicate to the rule set, assigning it to the given rule name. Raises KeyError if another rule with that name already exists.
set_rule(name, predicate)
Set the rule with the given name, regardless if one already exists.
remove_rule(name)
Remove the rule with the given name. Raises KeyError if a rule with that name does not exist.
rule_exists(name)
Returns True if a rule with the given name exists, False otherwise.
test_rule(name, obj=None, target=None)
Returns the result of calling predicate.test(obj, target) where predicate is the predicate for the rule with the given name. Returns False if a rule with the given name does not exist.

Decorators

@predicate

Decorator that creates a predicate out of any callable:

>>> @predicate
... def is_book_author(user, book):
...     return book.author == user
...
>>> is_book_author
<Predicate:is_book_author object at 0x10eeaa490>

Customising the predicate name:

>>> @predicate(name='another_name')
... def is_book_author(user, book):
...     return book.author == user
...
>>> is_book_author
<Predicate:another_name object at 0x10eeaa490>

Binding self:

>>> @predicate(bind=True)
... def is_book_author(self, user, book):
...     if 'user_has_special_flag' in self.context:
...         return self.context['user_has_special_flag']
...     return book.author == user

Predefined predicates

always_allow()always_true()
Always returns True.
always_deny()always_false()
Always returns False.
is_authenticated(user)
Returns the result of calling user.is_authenticated(). Returns False if the given user does not have an is_authenticated method.
is_superuser(user)
Returns the result of calling user.is_superuser. Returns False if the given user does not have an is_superuser property.
is_staff(user)
Returns the result of calling user.is_staff. Returns False if the given user does not have an is_staffproperty.
is_active(user)
Returns the result of calling user.is_active. Returns False if the given user does not have an is_active property.
is_group_member(*groups)
Factory that creates a new predicate that returns True if the given user is a member of all the given groups, False otherwise.

Shortcuts

Managing the shared rule set

add_rule(name, predicate)
Adds a rule to the shared rule set. See RuleSet.add_rule.
set_rule(name, predicate)
Set the rule with the given name from the shared rule set. See RuleSet.set_rule.
remove_rule(name)
Remove a rule from the shared rule set. See RuleSet.remove_rule.
rule_exists(name)
Returns whether a rule exists in the shared rule set. See RuleSet.rule_exists.
test_rule(name, obj=None, target=None)
Tests the rule with the given name. See RuleSet.test_rule.

Managing the permissions rule set

add_perm(name, predicate)
Adds a rule to the permissions rule set. See RuleSet.add_rule.
set_perm(name, predicate)
Replace a rule from the permissions rule set. See RuleSet.set_rule.
remove_perm(name)
Remove a rule from the permissions rule set. See RuleSet.remove_rule.
perm_exists(name)
Returns whether a rule exists in the permissions rule set. See RuleSet.rule_exists.
has_perm(name, user=None, obj=None)
Tests the rule with the given name. See RuleSet.test_rule.

Licence

django-rules is distributed under the MIT licence.

Copyright (c) 2014 Akis Kesoglou

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

原文地址:https://www.cnblogs.com/staff/p/10768519.html