[Python-modules-commits] [python-django-rules] 01/03: importing python-django-rules_1.2.1.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Mon May 15 08:57:04 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository python-django-rules.

commit e41e17af7567c0ed240567b19f7945c5b16fdfcd
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Sat May 13 21:59:44 2017 +0200

    importing python-django-rules_1.2.1.orig.tar.gz
---
 .coveragerc                                  |   8 +
 .gitignore                                   |  13 +
 .travis.yml                                  |  61 ++
 INSTALL                                      |   6 +
 LICENSE                                      |  22 +
 MANIFEST.in                                  |   7 +
 README.rst                                   | 990 +++++++++++++++++++++++++++
 rules/__init__.py                            |   9 +
 rules/apps.py                                |  11 +
 rules/compat/__init__.py                     |   0
 rules/compat/access_mixins.py                | 150 ++++
 rules/contrib/__init__.py                    |   0
 rules/contrib/admin.py                       |  49 ++
 rules/contrib/views.py                       | 159 +++++
 rules/permissions.py                         |  31 +
 rules/predicates.py                          | 320 +++++++++
 rules/rulesets.py                            |  42 ++
 rules/templatetags/__init__.py               |   0
 rules/templatetags/rules.py                  |  19 +
 runtests.py                                  |  47 ++
 runtests.sh                                  |   7 +
 setup.cfg                                    |   5 +
 setup.py                                     |  78 +++
 tests/manage.py                              |  16 +
 tests/testapp/__init__.py                    |   0
 tests/testapp/admin.py                       |  12 +
 tests/testapp/migrations/0001_initial.py     |  28 +
 tests/testapp/migrations/__init__.py         |   0
 tests/testapp/models.py                      |  13 +
 tests/testapp/rules.py                       |  27 +
 tests/testapp/settings.py                    |  60 ++
 tests/testapp/templates/empty.html           |   1 +
 tests/testapp/urls.py                        |  25 +
 tests/testapp/views.py                       |  61 ++
 tests/testsuite/__init__.py                  |   0
 tests/testsuite/contrib/__init__.py          |  33 +
 tests/testsuite/contrib/test_admin.py        |  26 +
 tests/testsuite/contrib/test_predicates.py   |  44 ++
 tests/testsuite/contrib/test_templatetags.py |  95 +++
 tests/testsuite/contrib/test_views.py        | 131 ++++
 tests/testsuite/test_permissions.py          |  35 +
 tests/testsuite/test_predicates.py           | 443 ++++++++++++
 tests/testsuite/test_rulesets.py             |  41 ++
 tox.ini                                      |  22 +
 44 files changed, 3147 insertions(+)

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..6546a3e
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,8 @@
+[report]
+exclude_lines =
+    pragma: no cover
+omit =
+    */python?.?/*
+    */site-packages/nose/*
+    */rules/compat/*
+    */rules/apps.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1268f61
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.egg-info
+*.pyc
+*~
+._*
+.coverage
+.DS_Store
+.Python
+__pycache__
+dist/
+docs/_build
+pip-log.txt
+.tox
+.eggs/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6094061
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,61 @@
+# ----------------------------------------
+# Python support matrix per Django version
+#
+#   2.6   1.5  1.6
+#   2.7   1.5  1.6  1.7  1.8  1.9  1.10
+#   3.3   1.5  1.6  1.7  1.8
+#   3.4             1.7  1.8  1.9  1.10
+#   3.5                  1.8  1.9  1.10
+# ----------------------------------------
+sudo: false
+language: python
+python:
+  - "2.6"
+  - "2.7"
+  - "3.3"
+  - "3.4"
+  - "3.5"
+  - "pypy"
+  - "pypy3"
+env:
+  - DJANGO='Django<1.6'  # Django 1.5
+  - DJANGO='Django<1.7'  # Django 1.6
+  - DJANGO='Django<1.8'  # Django 1.7
+  - DJANGO='Django<1.9'  # Django 1.8
+  - DJANGO='Django<1.10' # Django 1.9
+  - DJANGO='Django<1.11' # Django 1.10
+matrix:
+  exclude:
+    - python: "2.6"
+      env: DJANGO='Django<1.8'
+    - python: "2.6"
+      env: DJANGO='Django<1.9'
+    - python: "2.6"
+      env: DJANGO='Django<1.10'
+    - python: "2.6"
+      env: DJANGO='Django<1.11'
+    - python: "3.3"
+      env: DJANGO='Django<1.10'
+    - python: "3.3"
+      env: DJANGO='Django<1.11'
+    - python: "3.4"
+      env: DJANGO='Django<1.6'
+    - python: "3.4"
+      env: DJANGO='Django<1.7'
+    - python: "3.5"
+      env: DJANGO='Django<1.6'
+    - python: "3.5"
+      env: DJANGO='Django<1.7'
+    - python: "3.5"
+      env: DJANGO='Django<1.8'
+    - python: "pypy3"
+      env: DJANGO='Django<1.10'
+    - python: "pypy3"
+      env: DJANGO='Django<1.11'
+install:
+  - 'pip install "${DJANGO}"'
+  - pip install coveralls
+  - pip install -e .
+script: ./runtests.sh -v
+after_success:
+  - coveralls
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..52a88a2
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,6 @@
+
+Please use::
+
+    $ python setup.py install
+
+See README.rst
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..939d19b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..0cab32c
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,7 @@
+include INSTALL
+include LICENSE
+include README.rst
+recursive-include tests *
+global-exclude *.pyc
+global-exclude .coverage
+global-exclude .DS_Store
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..8ff6d08
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,990 @@
+django-rules
+^^^^^^^^^^^^
+
+``rules`` is a tiny but powerful app providing object-level permissions to
+Django, without requiring a database. At its core, it is a generic framework
+for building rule-based systems, similar to `decision trees`_. It can also be
+used as a standalone library in other contexts and frameworks.
+
+.. image:: https://travis-ci.org/dfunckt/django-rules.svg?branch=master
+    :target: https://travis-ci.org/dfunckt/django-rules
+.. image:: https://coveralls.io/repos/dfunckt/django-rules/badge.svg
+    :target: https://coveralls.io/r/dfunckt/django-rules
+.. image:: https://img.shields.io/pypi/v/rules.svg
+    :target: https://pypi.python.org/pypi/rules
+.. image:: https://img.shields.io/pypi/pyversions/rules.svg
+    :target: https://pypi.python.org/pypi/rules
+
+.. _decision trees: http://wikipedia.org/wiki/Decision_tree
+
+
+Features
+========
+
+``rules`` has got you covered. ``rules`` is:
+
+-   **Documented**, **tested**, **reliable** and **easy to use**.
+-   **Versatile**. Decorate callables to build complex graphs of predicates.
+    Predicates can be any type of callable -- simple functions, lambdas,
+    methods, callable class objects, partial functions, decorated functions,
+    anything really.
+-   **A good Django citizen**. Seamless integration with Django views,
+    templates and the Admin for testing for object-level permissions.
+-   **Efficient** and **smart**. No need to mess around with a database to figure
+    out whether John really wrote that book.
+-   **Simple**. Dive in the code. You'll need 10 minutes to figure out how it
+    works.
+-   **Powerful**. ``rules`` comes complete with advanced features, such as
+    invocation context and storage for arbitrary data, skipping evaluation of
+    predicates under specific conditions, logging of evaluated predicates and more!
+
+
+Table of Contents
+=================
+
+- `Requirements`_
+- `How to install`_
+
+  - `Configuring Django`_
+
+- `Using Rules`_
+
+  - `Creating predicates`_
+  - `Setting up rules`_
+  - `Combining predicates`_
+
+- `Using Rules with Django`_
+
+  - `Permissions`_
+  - `Rules and permissions in views`_
+  - `Rules and permissions in templates`_
+  - `Rules and permissions in the Admin`_
+
+- `Advanced features`_
+
+  - `Custom rule sets`_
+  - `Invocation context`_
+  - `Binding "self"`_
+  - `Skipping predicates`_
+  - `Logging predicate evaluation`_
+
+- `Best practices`_
+- `API Reference`_
+- `Changelog`_
+- `Licence`_
+
+
+Requirements
+============
+
+``rules`` requires Python 2.6/3.3 or newer. It can optionally integrate with
+Django, in which case requires Django 1.5 or newer.
+
+
+How to install
+==============
+
+Using pip:
+
+.. code:: bash
+
+    $ pip install rules
+
+Manually:
+
+.. code:: bash
+
+    $ git clone https://github.com/dfunckt/django-rules.git
+    $ cd django-rules
+    $ python setup.py install
+
+Run tests with:
+
+.. code:: bash
+
+    $ ./runtests.sh
+
+You may also want to read `Best practices`_ for general advice on how to
+use ``rules``.
+
+
+Configuring Django
+------------------
+
+Add ``rules`` to ``INSTALLED_APPS``:
+
+.. code:: python
+
+    INSTALLED_APPS = (
+        # ...
+        'rules',
+    )
+
+Add the authentication backend:
+
+.. code:: python
+
+    AUTHENTICATION_BACKENDS = (
+        'rules.permissions.ObjectPermissionBackend',
+        'django.contrib.auth.backends.ModelBackend',
+    )
+
+
+Using Rules
+===========
+
+``rules`` is based on the idea that you maintain a dict-like object that maps
+string keys used as identifiers of some kind, to callables, called
+*predicates*. This dict-like object is actually an instance of ``RuleSet`` and
+the predicates are instances of ``Predicate``.
+
+
+Creating predicates
+-------------------
+
+Let's ignore rule sets for a moment and go ahead and define a predicate. The
+easiest way is with the ``@predicate`` decorator:
+
+.. code:: python
+
+    >>> @rules.predicate
+    >>> def is_book_author(user, book):
+    ...     return book.author == user
+    ...
+    >>> is_book_author
+    <Predicate:is_book_author object at 0x10eeaa490>
+
+This predicate will return ``True`` if the book's author is the given user,
+``False`` otherwise.
+
+Predicates can be created from any callable that accepts anything from zero to
+two positional arguments:
+
+*   ``fn(obj, target)``
+*   ``fn(obj)``
+*   ``fn()``
+
+This is their generic form. If seen from the perspective of authorization in
+Django, the equivalent signatures are:
+
+*   ``fn(user, obj)``
+*   ``fn(user)``
+*   ``fn()``
+
+Predicates can do pretty much anything with the given arguments, but must
+always return ``True`` if the condition they check is true, ``False``
+otherwise. ``rules`` comes with several predefined predicates that you may
+read about later on in `API Reference`_, that are mostly useful when dealing
+with `authorization in Django`_.
+
+
+Setting up rules
+----------------
+
+Let's pretend that we want to let authors edit or delete their books, but not
+books written by other authors. So, essentially, what determines whether an
+author *can edit* or *can delete* a given book is *whether they are its
+author*.
+
+In ``rules``, such requirements are modelled as *rules*. A *rule* is a map of
+a unique identifier (eg. "can edit") to a predicate. Rules are grouped
+together into a *rule set*. ``rules`` has two predefined rule sets:
+
+*   A default rule set storing shared rules.
+*   Another rule set storing rules that serve as permissions in a Django
+    context.
+
+So, let's define our first couple of rules, adding them to the shared rule
+set. We can use the ``is_book_author`` predicate we defined earlier:
+    
+.. code:: python
+
+    >>> rules.add_rule('can_edit_book', is_book_author)
+    >>> rules.add_rule('can_delete_book', is_book_author)
+
+Assuming we've got some data, we can now test our rules:
+
+.. code:: python
+
+    >>> from django.contrib.auth.models import User
+    >>> from books.models import Book
+    >>> guidetodjango = Book.objects.get(isbn='978-1-4302-1936-1')
+    >>> guidetodjango.author
+    <User: adrian>
+    >>> adrian = User.objects.get(username='adrian')
+    >>> rules.test_rule('can_edit_book', adrian, guidetodjango)
+    True
+    >>> rules.test_rule('can_delete_book', adrian, guidetodjango)
+    True
+
+Nice... but not awesome.
+
+
+Combining predicates
+--------------------
+
+Predicates by themselves are not so useful -- not more useful than any other
+function would be. Predicates, however, can be combined using binary operators
+to create more complex ones. Predicates support the following operators:
+
+*   ``P1 & P2``: Returns a new predicate that returns ``True`` if *both*
+    predicates return ``True``, otherwise ``False``. If P1 returns ``False``,
+    P2 will not be evaluated.
+*   ``P1 | P2``: Returns a new predicate that returns ``True`` if *any* of the
+    predicates returns ``True``, otherwise ``False``. If P1 returns ``True``,
+    P2 will not be evaluated.
+*   ``P1 ^ P2``: Returns a new predicate that returns ``True`` if one of the
+    predicates returns ``True`` and the other returns ``False``, otherwise
+    ``False``.
+*   ``~P``: Returns a new predicate that returns the negated result of the
+    original predicate.
+
+Suppose the requirement for allowing a user to edit a given book was for them
+to be either the book's author, or a member of the "editors" group. Allowing
+users to delete a book should still be determined by whether the user is the
+book's author.
+
+With ``rules`` that's easy to implement. We'd have to define another
+predicate, that would return ``True`` if the given user is a member of the
+"editors" group, ``False`` otherwise. The built-in ``is_group_member`` factory
+will come in handy:
+
+.. code:: python
+
+    >>> is_editor = rules.is_group_member('editors')
+    >>> is_editor
+    <Predicate:is_group_member:editors object at 0x10eee1350>
+
+We could combine it with the ``is_book_author`` predicate to create a new one
+that checks for either condition:
+
+.. code:: python
+
+    >>> 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>
+
+We can now update our ``can_edit_book`` rule:
+
+.. code:: python
+
+    >>> rules.add_rule('can_edit_book', is_book_author_or_editor)
+    Traceback (most recent call last):
+        ...
+    KeyError: A rule with name `can_edit_book` already exists
+    >>> rules.remove_rule('can_edit_book')
+    >>> rules.add_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
+
+Let's see what happens with another user:
+
+.. code:: python
+
+    >>> 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
+
+Awesome.
+
+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.
+
+
+.. _authorization in Django:
+
+Using Rules with Django
+=======================
+
+``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
+-----------
+
+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
+++++++++++++++++++++
+
+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:
+
+.. code:: python
+
+    >>> rules.add_perm('books.change_book', is_book_author | is_editor)
+    >>> rules.add_perm('books.delete_book', is_book_author)
+
+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
++++++++++++++++++++++++
+
+Let's go ahead and check whether ``adrian`` has change permission to the
+``guidetodjango`` book:
+
+.. code:: python
+
+    >>> adrian.has_perm('books.change_book', guidetodjango)
+    False
+
+When you call the ``User.has_perm`` method, Django asks each backend in
+``settings.AUTHENTICATION_BACKENDS`` whether a user has the given permission
+for the object. When queried for object permissions, Django's default
+authentication backend always returns ``False``. ``rules`` 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:
+
+.. code:: python
+
+    AUTHENTICATION_BACKENDS = (
+        'rules.permissions.ObjectPermissionBackend',
+        'django.contrib.auth.backends.ModelBackend',
+    )
+
+Now, checking again gives ``adrian`` the required permissions:
+
+.. code:: python
+
+    >>> 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
+
+
+Rules and permissions in views
+------------------------------
+
+``rules`` comes with a set of view decorators to help you enforce
+authorization in your views.
+
+Using the function-based view decorator
++++++++++++++++++++++++++++++++++++++++
+
+For function-based views you can use the ``permission_required`` decorator:
+
+.. code:: python
+
+    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):
+        # ...
+
+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:
+
+.. code:: python
+
+    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):
+        # ...    
+
+For more information on the decorator and helper function, refer to the
+``rules.contrib.views`` module.
+
+Using the class-based view mixin
+++++++++++++++++++++++++++++++++
+
+Django 1.9 introduced a new set of access mixins that you can use in your
+class-based views to enforce authorization. ``rules`` extends this framework
+to provide a mixin for object-level permissions, ``PermissionRequiredMixin``.
+Note that ``rules`` will seamlessly fall back to importing its own copy of
+Django's access mixins module for versions of Django prior to 1.9.
+
+The following example will automatically test for permission against the
+instance returned by the view's ``get_object`` method:
+
+.. code:: python
+
+    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'
+
+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.
+
+.. _Django documentation: https://docs.djangoproject.com/en/1.9/topics/auth/default/#limiting-access-to-logged-in-users
+
+Rules and permissions in templates
+----------------------------------
+
+``rules`` comes with two template tags to allow you to test for rules and
+permissions in templates.
+
+Add ``rules`` to your ``INSTALLED_APPS``:
+
+.. code:: python
+
+    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 %}
+
+
+Rules and permissions in the 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>.change_<modelname>``
+- ``<app_label>.delete_<modelname>``
+- ``<app_label>``
+
+The first three are obvious. The fourth 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:
+
+.. code:: python
+
+    >>> rules.add_perm('books', rules.always_allow)
+    >>> rules.add_perm('books.add_book', is_staff)
+    >>> rules.add_perm('books.change_book', is_staff)
+    >>> rules.add_perm('books.delete_book', is_staff)
+
+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_change_permission(user, obj=None)``
+- ``has_delete_permission(user, obj=None)``
+
+**Note:** There's also ``has_add_permission(user)`` but is not relevant here.
+
+``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:
+
+.. code:: python
+
+    # 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:
+
+.. code:: python
+
+    >>> 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)
+
+
+Advanced features
+=================
+
+Custom rule sets
+----------------
+
+You may create as many rule sets as you need:
+
+.. code:: python
+
+    >>> features = rules.RuleSet()
+
+And manipulate them by adding, removing, querying and testing rules:
+
+.. code:: python
+
+    >>> 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
+------------------
+
+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:
+
+.. code:: python
+
+    >>> @predicate
+    ... def mypred(a, b):
+    ...     value = compute_expensive_value(a)
+    ...     mypred.context['value'] = value
+    ...     return True
+
+Other predicates can later use stored values:
+
+.. code:: python
+
+    >>> @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"
+--------------
+
+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:
+
+.. code:: python
+
+    >>> @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:
+
+.. code:: python
+
+    >>> @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.
+
+**Note:** This is new in version 1.1.0. It was possible to skip predicates in
+older versions by calling the predicate's ``skip()`` method, but this has been
+deprecated and support will be completely removed in a future version.
+
+
+Logging predicate evaluation
+----------------------------
+
+``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 `dictConfig`_ configures
+a console logger (place this in your project's `settings.py` if you're using
+`rules` with Django): 
+
+.. code:: python
+
+    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.
+
+.. _dictConfig: https://docs.python.org/3.6/library/logging.config.html#logging-config-dictschema
+
+
+Best practices
+==============
+
+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.
+
+If using Django 1.7 and later, ``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:
+
+.. code:: python
+
+    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
+``django-rules`` itself:
+
+.. code:: python
+
+    from __future__ import absolute_import
+
+
+API Reference
+=============
+
+Everything is accessible from the root ``rules`` module.
+
+
+Class ``rules.Predicate``
+-------------------------
+
+You create ``Predicate`` instances by passing in a callable:
+
+.. code:: python
+
+    >>> 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:
+
+.. code:: python
+
+    >>> 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``:
+
+.. code:: python
+
+    >>> 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.
+
+.. _dict: http://docs.python.org/library/stdtypes.html#mapping-types-dict
... 2446 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-rules.git



More information about the Python-modules-commits mailing list