• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

rules/H07-Dec-2018-667468

rules.egg-info/H03-May-2022-1,001711

tests/H07-Dec-2018-1,140824

CHANGELOG.mdH A D07-Dec-20182.4 KiB8760

INSTALLH A D07-Dec-201861 73

LICENSEH A D07-Dec-20181 KiB2319

MANIFEST.inH A D07-Dec-2018169 98

PKG-INFOH A D07-Dec-201838.6 KiB1,001711

README.rstH A D07-Dec-201830 KiB976687

setup.cfgH A D07-Dec-2018195 1813

setup.pyH A D07-Dec-20181.8 KiB7155

README.rst

1rules
2^^^^^
3
4``rules`` is a tiny but powerful app providing object-level permissions to
5Django, without requiring a database. At its core, it is a generic framework
6for building rule-based systems, similar to `decision trees`_. It can also be
7used as a standalone library in other contexts and frameworks.
8
9.. image:: https://travis-ci.org/dfunckt/django-rules.svg?branch=master
10    :target: https://travis-ci.org/dfunckt/django-rules
11.. image:: https://coveralls.io/repos/dfunckt/django-rules/badge.svg
12    :target: https://coveralls.io/r/dfunckt/django-rules
13.. image:: https://img.shields.io/pypi/v/rules.svg
14    :target: https://pypi.python.org/pypi/rules
15.. image:: https://img.shields.io/pypi/pyversions/rules.svg
16    :target: https://pypi.python.org/pypi/rules
17
18.. _decision trees: http://wikipedia.org/wiki/Decision_tree
19
20
21Features
22========
23
24``rules`` has got you covered. ``rules`` is:
25
26-   **Documented**, **tested**, **reliable** and **easy to use**.
27-   **Versatile**. Decorate callables to build complex graphs of predicates.
28    Predicates can be any type of callable -- simple functions, lambdas,
29    methods, callable class objects, partial functions, decorated functions,
30    anything really.
31-   **A good Django citizen**. Seamless integration with Django views,
32    templates and the Admin for testing for object-level permissions.
33-   **Efficient** and **smart**. No need to mess around with a database to figure
34    out whether John really wrote that book.
35-   **Simple**. Dive in the code. You'll need 10 minutes to figure out how it
36    works.
37-   **Powerful**. ``rules`` comes complete with advanced features, such as
38    invocation context and storage for arbitrary data, skipping evaluation of
39    predicates under specific conditions, logging of evaluated predicates and more!
40
41
42Table of Contents
43=================
44
45- `Requirements`_
46- `Upgrading from 1.x`_
47- `How to install`_
48
49  - `Configuring Django`_
50
51- `Using Rules`_
52
53  - `Creating predicates`_
54  - `Setting up rules`_
55  - `Combining predicates`_
56
57- `Using Rules with Django`_
58
59  - `Permissions`_
60  - `Rules and permissions in views`_
61  - `Rules and permissions in templates`_
62  - `Rules and permissions in the Admin`_
63
64- `Advanced features`_
65
66  - `Custom rule sets`_
67  - `Invocation context`_
68  - `Binding "self"`_
69  - `Skipping predicates`_
70  - `Logging predicate evaluation`_
71
72- `Best practices`_
73- `API Reference`_
74- `Licence`_
75
76
77Requirements
78============
79
80``rules`` requires Python 2.7/3.4 or newer. It can optionally integrate with
81Django, in which case requires Django 1.11 or newer.
82
83*Note*: At any given moment in time, ``rules`` will maintain support for all
84currently supported Django versions, while dropping support for those versions
85that reached end-of-life in minor releases. See the `Supported Versions`_
86section on Django Project website for the current state and timeline.
87
88.. _Supported Versions: https://www.djangoproject.com/download/#supported-versions
89
90
91Upgrading from 1.x
92==================
93
94*   Support Python 2.6 and 3.3, and Django versions before 1.11 has been
95    dropped.
96
97*   The ``SkipPredicate`` exception and ``skip()`` method of ``Predicate``,
98    that were used to signify that a predicate should be skipped, have been
99    removed. You may return ``None`` from your predicate to achieve this.
100
101*   The APIs to replace a rule's predicate have been renamed and their
102    behaviour changed. ``replace_rule`` and ``replace_perm`` functions and
103    ``replace_rule`` method of ``RuleSet`` have been renamed to ``set_rule``,
104    ``set_perm`` and ``RuleSet.set_perm`` respectively. The old behaviour was
105    to raise a ``KeyError`` if a rule by the given name did not exist. Since
106    version 2.0 this has changed and you can safely use ``set_*`` to set a
107    rule's predicate without having to ensure the rule exists first.
108
109
110How to install
111==============
112
113Using pip:
114
115.. code:: bash
116
117    $ pip install rules
118
119Manually:
120
121.. code:: bash
122
123    $ git clone https://github.com/dfunckt/django-rules.git
124    $ cd django-rules
125    $ python setup.py install
126
127Run tests with:
128
129.. code:: bash
130
131    $ ./runtests.sh
132
133You may also want to read `Best practices`_ for general advice on how to
134use ``rules``.
135
136
137Configuring Django
138------------------
139
140Add ``rules`` to ``INSTALLED_APPS``:
141
142.. code:: python
143
144    INSTALLED_APPS = (
145        # ...
146        'rules',
147    )
148
149Add the authentication backend:
150
151.. code:: python
152
153    AUTHENTICATION_BACKENDS = (
154        'rules.permissions.ObjectPermissionBackend',
155        'django.contrib.auth.backends.ModelBackend',
156    )
157
158
159Using Rules
160===========
161
162``rules`` is based on the idea that you maintain a dict-like object that maps
163string keys used as identifiers of some kind, to callables, called
164*predicates*. This dict-like object is actually an instance of ``RuleSet`` and
165the predicates are instances of ``Predicate``.
166
167
168Creating predicates
169-------------------
170
171Let's ignore rule sets for a moment and go ahead and define a predicate. The
172easiest way is with the ``@predicate`` decorator:
173
174.. code:: python
175
176    >>> @rules.predicate
177    >>> def is_book_author(user, book):
178    ...     return book.author == user
179    ...
180    >>> is_book_author
181    <Predicate:is_book_author object at 0x10eeaa490>
182
183This predicate will return ``True`` if the book's author is the given user,
184``False`` otherwise.
185
186Predicates can be created from any callable that accepts anything from zero to
187two positional arguments:
188
189*   ``fn(obj, target)``
190*   ``fn(obj)``
191*   ``fn()``
192
193This is their generic form. If seen from the perspective of authorization in
194Django, the equivalent signatures are:
195
196*   ``fn(user, obj)``
197*   ``fn(user)``
198*   ``fn()``
199
200Predicates can do pretty much anything with the given arguments, but must
201always return ``True`` if the condition they check is true, ``False``
202otherwise. ``rules`` comes with several predefined predicates that you may
203read about later on in `API Reference`_, that are mostly useful when dealing
204with `authorization in Django`_.
205
206
207Setting up rules
208----------------
209
210Let's pretend that we want to let authors edit or delete their books, but not
211books written by other authors. So, essentially, what determines whether an
212author *can edit* or *can delete* a given book is *whether they are its
213author*.
214
215In ``rules``, such requirements are modelled as *rules*. A *rule* is a map of
216a unique identifier (eg. "can edit") to a predicate. Rules are grouped
217together into a *rule set*. ``rules`` has two predefined rule sets:
218
219*   A default rule set storing shared rules.
220*   Another rule set storing rules that serve as permissions in a Django
221    context.
222
223So, let's define our first couple of rules, adding them to the shared rule
224set. We can use the ``is_book_author`` predicate we defined earlier:
225
226.. code:: python
227
228    >>> rules.add_rule('can_edit_book', is_book_author)
229    >>> rules.add_rule('can_delete_book', is_book_author)
230
231Assuming we've got some data, we can now test our rules:
232
233.. code:: python
234
235    >>> from django.contrib.auth.models import User
236    >>> from books.models import Book
237    >>> guidetodjango = Book.objects.get(isbn='978-1-4302-1936-1')
238    >>> guidetodjango.author
239    <User: adrian>
240    >>> adrian = User.objects.get(username='adrian')
241    >>> rules.test_rule('can_edit_book', adrian, guidetodjango)
242    True
243    >>> rules.test_rule('can_delete_book', adrian, guidetodjango)
244    True
245
246Nice... but not awesome.
247
248
249Combining predicates
250--------------------
251
252Predicates by themselves are not so useful -- not more useful than any other
253function would be. Predicates, however, can be combined using binary operators
254to create more complex ones. Predicates support the following operators:
255
256*   ``P1 & P2``: Returns a new predicate that returns ``True`` if *both*
257    predicates return ``True``, otherwise ``False``. If P1 returns ``False``,
258    P2 will not be evaluated.
259*   ``P1 | P2``: Returns a new predicate that returns ``True`` if *any* of the
260    predicates returns ``True``, otherwise ``False``. If P1 returns ``True``,
261    P2 will not be evaluated.
262*   ``P1 ^ P2``: Returns a new predicate that returns ``True`` if one of the
263    predicates returns ``True`` and the other returns ``False``, otherwise
264    ``False``.
265*   ``~P``: Returns a new predicate that returns the negated result of the
266    original predicate.
267
268Suppose the requirement for allowing a user to edit a given book was for them
269to be either the book's author, or a member of the "editors" group. Allowing
270users to delete a book should still be determined by whether the user is the
271book's author.
272
273With ``rules`` that's easy to implement. We'd have to define another
274predicate, that would return ``True`` if the given user is a member of the
275"editors" group, ``False`` otherwise. The built-in ``is_group_member`` factory
276will come in handy:
277
278.. code:: python
279
280    >>> is_editor = rules.is_group_member('editors')
281    >>> is_editor
282    <Predicate:is_group_member:editors object at 0x10eee1350>
283
284We could combine it with the ``is_book_author`` predicate to create a new one
285that checks for either condition:
286
287.. code:: python
288
289    >>> is_book_author_or_editor = is_book_author | is_editor
290    >>> is_book_author_or_editor
291    <Predicate:(is_book_author | is_group_member:editors) object at 0x10eee1390>
292
293We can now update our ``can_edit_book`` rule:
294
295.. code:: python
296
297    >>> rules.set_rule('can_edit_book', is_book_author_or_editor)
298    >>> rules.test_rule('can_edit_book', adrian, guidetodjango)
299    True
300    >>> rules.test_rule('can_delete_book', adrian, guidetodjango)
301    True
302
303Let's see what happens with another user:
304
305.. code:: python
306
307    >>> martin = User.objects.get(username='martin')
308    >>> list(martin.groups.values_list('name', flat=True))
309    ['editors']
310    >>> rules.test_rule('can_edit_book', martin, guidetodjango)
311    True
312    >>> rules.test_rule('can_delete_book', martin, guidetodjango)
313    False
314
315Awesome.
316
317So far, we've only used the underlying, generic framework for defining and
318testing rules. This layer is not at all specific to Django; it may be used in
319any context. There's actually no import of anything Django-related in the
320whole app (except in the ``rules.templatetags`` module). ``rules`` however can
321integrate tightly with Django to provide authorization.
322
323
324.. _authorization in Django:
325
326Using Rules with Django
327=======================
328
329``rules`` is able to provide object-level permissions in Django. It comes
330with an authorization backend and a couple template tags for use in your
331templates.
332
333
334Permissions
335-----------
336
337In ``rules``, permissions are a specialised type of rules. You still define
338rules by creating and combining predicates. These rules however, must be added
339to a permissions-specific rule set that comes with ``rules`` so that they can
340be picked up by the ``rules`` authorization backend.
341
342
343Creating permissions
344++++++++++++++++++++
345
346The convention for naming permissions in Django is ``app_label.action_object``,
347and we like to adhere to that. Let's add rules for the ``books.change_book``
348and ``books.delete_book`` permissions:
349
350.. code:: python
351
352    >>> rules.add_perm('books.change_book', is_book_author | is_editor)
353    >>> rules.add_perm('books.delete_book', is_book_author)
354
355See the difference in the API? ``add_perm`` adds to a permissions-specific
356rule set, whereas ``add_rule`` adds to a default shared rule set. It's
357important to know however, that these two rule sets are separate, meaning that
358adding a rule in one does not make it available to the other.
359
360
361Checking for permission
362+++++++++++++++++++++++
363
364Let's go ahead and check whether ``adrian`` has change permission to the
365``guidetodjango`` book:
366
367.. code:: python
368
369    >>> adrian.has_perm('books.change_book', guidetodjango)
370    False
371
372When you call the ``User.has_perm`` method, Django asks each backend in
373``settings.AUTHENTICATION_BACKENDS`` whether a user has the given permission
374for the object. When queried for object permissions, Django's default
375authentication backend always returns ``False``. ``rules`` comes with an
376authorization backend, that is able to provide object-level permissions by
377looking into the permissions-specific rule set.
378
379Let's add the ``rules`` authorization backend in settings:
380
381.. code:: python
382
383    AUTHENTICATION_BACKENDS = (
384        'rules.permissions.ObjectPermissionBackend',
385        'django.contrib.auth.backends.ModelBackend',
386    )
387
388Now, checking again gives ``adrian`` the required permissions:
389
390.. code:: python
391
392    >>> adrian.has_perm('books.change_book', guidetodjango)
393    True
394    >>> adrian.has_perm('books.delete_book', guidetodjango)
395    True
396    >>> martin.has_perm('books.change_book', guidetodjango)
397    True
398    >>> martin.has_perm('books.delete_book', guidetodjango)
399    False
400
401
402Rules and permissions in views
403------------------------------
404
405``rules`` comes with a set of view decorators to help you enforce
406authorization in your views.
407
408Using the function-based view decorator
409+++++++++++++++++++++++++++++++++++++++
410
411For function-based views you can use the ``permission_required`` decorator:
412
413.. code:: python
414
415    from django.shortcuts import get_object_or_404
416    from rules.contrib.views import permission_required
417    from posts.models import Post
418
419    def get_post_by_pk(request, post_id):
420        return get_object_or_404(Post, pk=post_id)
421
422    @permission_required('posts.change_post', fn=get_post_by_pk)
423    def post_update(request, post_id):
424        # ...
425
426Usage is straight-forward, but there's one thing in the example above that
427stands out and this is the ``get_post_by_pk`` function. This function, given
428the current request and all arguments passed to the view, is responsible for
429fetching and returning the object to check permissions against -- i.e. the
430``Post`` instance with PK equal to the given ``post_id`` in the example.
431This specific use-case is quite common so, to save you some typing, ``rules``
432comes with a generic helper function that you can use to do this declaratively.
433The example below is equivalent to the one above:
434
435.. code:: python
436
437    from rules.contrib.views import permission_required, objectgetter
438    from posts.models import Post
439
440    @permission_required('posts.change_post', fn=objectgetter(Post, 'post_id'))
441    def post_update(request, post_id):
442        # ...
443
444For more information on the decorator and helper function, refer to the
445``rules.contrib.views`` module.
446
447Using the class-based view mixin
448++++++++++++++++++++++++++++++++
449
450Django includes a set of access mixins that you can use in your class-based
451views to enforce authorization. ``rules`` extends this framework to provide
452object-level permissions via a mixin, ``PermissionRequiredMixin``.
453
454The following example will automatically test for permission against the
455instance returned by the view's ``get_object`` method:
456
457.. code:: python
458
459    from django.views.generic.edit import UpdateView
460    from rules.contrib.views import PermissionRequiredMixin
461    from posts.models import Post
462
463    class PostUpdate(PermissionRequiredMixin, UpdateView):
464        model = Post
465        permission_required = 'posts.change_post'
466
467You can customise the object either by overriding ``get_object`` or
468``get_permission_object``.
469
470For more information refer to the `Django documentation`_ and the
471``rules.contrib.views`` module.
472
473.. _Django documentation: https://docs.djangoproject.com/en/1.9/topics/auth/default/#limiting-access-to-logged-in-users
474
475Rules and permissions in templates
476----------------------------------
477
478``rules`` comes with two template tags to allow you to test for rules and
479permissions in templates.
480
481Add ``rules`` to your ``INSTALLED_APPS``:
482
483.. code:: python
484
485    INSTALLED_APPS = (
486        # ...
487        'rules',
488    )
489
490Then, in your template::
491
492    {% load rules %}
493
494    {% has_perm 'books.change_book' author book as can_edit_book %}
495    {% if can_edit_book %}
496        ...
497    {% endif %}
498
499    {% test_rule 'has_super_feature' user as has_super_feature %}
500    {% if has_super_feature %}
501        ...
502    {% endif %}
503
504
505Rules and permissions in the Admin
506----------------------------------
507
508If you've setup ``rules`` to be used with permissions in Django, you're almost
509set to also use ``rules`` to authorize any add/change/delete actions in the
510Admin. The Admin asks for *four* different permissions, depending on action:
511
512- ``<app_label>.add_<modelname>``
513- ``<app_label>.view_<modelname>``
514- ``<app_label>.change_<modelname>``
515- ``<app_label>.delete_<modelname>``
516- ``<app_label>``
517
518*Note:* view permission is new in Django v2.1.
519
520The first four are obvious. The fifth is the required permission for an app
521to be displayed in the Admin's "dashboard". Here's some rules for our
522imaginary ``books`` app as an example:
523
524.. code:: python
525
526    >>> rules.add_perm('books', rules.always_allow)
527    >>> rules.add_perm('books.add_book', is_staff)
528    >>> rules.add_perm('books.view_book', is_staff | has_secret_access_code)
529    >>> rules.add_perm('books.change_book', is_staff)
530    >>> rules.add_perm('books.delete_book', is_staff)
531
532Django Admin does not support object-permissions, in the sense that it will
533never ask for permission to perform an action *on an object*, only whether a
534user is allowed to act on (*any*) instances of a model.
535
536If you'd like to tell Django whether a user has permissions on a specific
537object, you'd have to override the following methods of a model's
538``ModelAdmin``:
539
540- ``has_view_permission(user, obj=None)``
541- ``has_change_permission(user, obj=None)``
542- ``has_delete_permission(user, obj=None)``
543
544``rules`` comes with a custom ``ModelAdmin`` subclass,
545``rules.contrib.admin.ObjectPermissionsModelAdmin``, that overrides these
546methods to pass on the edited model instance to the authorization backends,
547thus enabling permissions per object in the Admin:
548
549.. code:: python
550
551    # books/admin.py
552    from django.contrib import admin
553    from rules.contrib.admin import ObjectPermissionsModelAdmin
554    from .models import Book
555
556    class BookAdmin(ObjectPermissionsModelAdmin):
557        pass
558
559    admin.site.register(Book, BookAdmin)
560
561Now this allows you to specify permissions like this:
562
563.. code:: python
564
565    >>> rules.add_perm('books', rules.always_allow)
566    >>> rules.add_perm('books.add_book', has_author_profile)
567    >>> rules.add_perm('books.change_book', is_book_author_or_editor)
568    >>> rules.add_perm('books.delete_book', is_book_author)
569
570To preserve backwards compatibility, Django will ask for either *view* or
571*change* permission. For maximum flexibility, ``rules`` behaves subtly
572different: ``rules`` will ask for the change permission if and only if no rule
573exists for the view permission.
574
575
576Advanced features
577=================
578
579Custom rule sets
580----------------
581
582You may create as many rule sets as you need:
583
584.. code:: python
585
586    >>> features = rules.RuleSet()
587
588And manipulate them by adding, removing, querying and testing rules:
589
590.. code:: python
591
592    >>> features.rule_exists('has_super_feature')
593    False
594    >>> is_special_user = rules.is_group_member('special')
595    >>> features.add_rule('has_super_feature', is_special_user)
596    >>> 'has_super_feature' in features
597    True
598    >>> features['has_super_feature']
599    <Predicate:is_group_member:special object at 0x10eeaa500>
600    >>> features.test_rule('has_super_feature', adrian)
601    True
602    >>> features.remove_rule('has_super_feature')
603
604Note however that custom rule sets are *not available* in Django templates --
605you need to provide integration yourself.
606
607
608Invocation context
609------------------
610
611A new context is created as a result of invoking ``Predicate.test()`` and is
612only valid for the duration of the invocation. A context is a simple ``dict``
613that you can use to store arbitrary data, (eg. caching computed values,
614setting flags, etc.), that can be used by predicates later on in the chain.
615Inside a predicate function it can be used like so:
616
617.. code:: python
618
619    >>> @predicate
620    ... def mypred(a, b):
621    ...     value = compute_expensive_value(a)
622    ...     mypred.context['value'] = value
623    ...     return True
624
625Other predicates can later use stored values:
626
627.. code:: python
628
629    >>> @predicate
630    ... def myotherpred(a, b):
631    ...     value = myotherpred.context.get('value')
632    ...     if value is not None:
633    ...         return do_something_with_value(value)
634    ...     else:
635    ...         return do_something_without_value()
636
637``Predicate.context`` provides a single ``args`` attribute that contains the
638arguments as given to ``test()`` at the beginning of the invocation.
639
640
641Binding "self"
642--------------
643
644In a predicate's function body, you can refer to the predicate instance itself
645by its name, eg. ``is_book_author``. Passing ``bind=True`` as a keyword
646argument to the ``predicate`` decorator will let you refer to the predicate
647with ``self``, which is more convenient. Binding ``self`` is just syntactic
648sugar. As a matter of fact, the following two are equivalent:
649
650.. code:: python
651
652    >>> @predicate
653    ... def is_book_author(user, book):
654    ...     if is_book_author.context.args:
655    ...         return user == book.author
656    ...     return False
657
658    >>> @predicate(bind=True)
659    ... def is_book_author(self, user, book):
660    ...     if self.context.args:
661    ...         return user == book.author
662    ...     return False
663
664
665Skipping predicates
666-------------------
667
668You may skip evaluation by returning ``None`` from your predicate:
669
670.. code:: python
671
672    >>> @predicate(bind=True)
673    ... def is_book_author(self, user, book):
674    ...     if len(self.context.args) > 1:
675    ...         return user == book.author
676    ...     else:
677    ...         return None
678
679Returning ``None`` signifies that the predicate need not be evaluated, thus
680leaving the predicate result up to that point unchanged.
681
682
683Logging predicate evaluation
684----------------------------
685
686``rules`` can optionally be configured to log debug information as rules are
687evaluated to help with debugging your predicates. Messages are sent at the
688DEBUG level to the ``'rules'`` logger. The following `dictConfig`_ configures
689a console logger (place this in your project's `settings.py` if you're using
690`rules` with Django):
691
692.. code:: python
693
694    LOGGING = {
695        'version': 1,
696        'disable_existing_loggers': False,
697        'handlers': {
698            'console': {
699                'level': 'DEBUG',
700                'class': 'logging.StreamHandler',
701            },
702        },
703        'loggers': {
704            'rules': {
705                'handlers': ['console'],
706                'level': 'DEBUG',
707                'propagate': True,
708            },
709        },
710    }
711
712When this logger is active each individual predicate will have a log message
713printed when it is evaluated.
714
715.. _dictConfig: https://docs.python.org/3.6/library/logging.config.html#logging-config-dictschema
716
717
718Best practices
719==============
720
721Before you can test for rules, these rules must be registered with a rule set,
722and for this to happen the modules containing your rule definitions must be
723imported.
724
725For complex projects with several predicates and rules, it may not be
726practical to define all your predicates and rules inside one module. It might
727be best to split them among any sub-components of your project. In a Django
728context, these sub-components could be the apps for your project.
729
730On the other hand, because importing predicates from all over the place in
731order to define rules can lead to circular imports and broken hearts, it's
732best to further split predicates and rules in different modules.
733
734``rules`` may optionally be configured to autodiscover ``rules.py`` modules in
735your apps and import them at startup. To have ``rules`` do so, just edit your
736``INSTALLED_APPS`` setting:
737
738.. code:: python
739
740    INSTALLED_APPS = (
741        # replace 'rules' with:
742        'rules.apps.AutodiscoverRulesConfig',
743    )
744
745**Note:** On Python 2, you must also add the following to the top of your
746``rules.py`` file, or you'll get import errors trying to import ``rules``
747itself:
748
749.. code:: python
750
751    from __future__ import absolute_import
752
753
754API Reference
755=============
756
757The core APIs are accessible from the root ``rules`` module. Django-specific
758functionality for the Admin and views is available from ``rules.contrib``.
759
760
761Class ``rules.Predicate``
762-------------------------
763
764You create ``Predicate`` instances by passing in a callable:
765
766.. code:: python
767
768    >>> def is_book_author(user, book):
769    ...     return book.author == user
770    ...
771    >>> pred = Predicate(is_book_author)
772    >>> pred
773    <Predicate:is_book_author object at 0x10eeaa490>
774
775You may optionally provide a different name for the predicate that is used
776when inspecting it:
777
778.. code:: python
779
780    >>> pred = Predicate(is_book_author, name='another_name')
781    >>> pred
782    <Predicate:another_name object at 0x10eeaa490>
783
784Also, you may optionally provide ``bind=True`` in order to be able to access
785the predicate instance with ``self``:
786
787.. code:: python
788
789    >>> def is_book_author(self, user, book):
790    ...     if self.context.args:
791    ...         return user == book.author
792    ...     return False
793    ...
794    >>> pred = Predicate(is_book_author, bind=True)
795    >>> pred
796    <Predicate:is_book_author object at 0x10eeaa490>
797
798
799Instance methods
800++++++++++++++++
801
802``test(obj=None, target=None)``
803    Returns the result of calling the passed in callable with zero, one or two
804    positional arguments, depending on how many it accepts.
805
806
807Class ``rules.RuleSet``
808-----------------------
809
810``RuleSet`` extends Python's built-in `dict`_ type. Therefore, you may create
811and use a rule set any way you'd use a dict.
812
813.. _dict: http://docs.python.org/library/stdtypes.html#mapping-types-dict
814
815
816Instance methods
817++++++++++++++++
818
819``add_rule(name, predicate)``
820    Adds a predicate to the rule set, assigning it to the given rule name.
821    Raises ``KeyError`` if another rule with that name already exists.
822
823``set_rule(name, predicate)``
824    Set the rule with the given name, regardless if one already exists.
825
826``remove_rule(name)``
827    Remove the rule with the given name. Raises ``KeyError`` if a rule with
828    that name does not exist.
829
830``rule_exists(name)``
831    Returns ``True`` if a rule with the given name exists, ``False`` otherwise.
832
833``test_rule(name, obj=None, target=None)``
834    Returns the result of calling ``predicate.test(obj, target)`` where
835    ``predicate`` is the predicate for the rule with the given name. Returns
836    ``False`` if a rule with the given name does not exist.
837
838Decorators
839----------
840
841``@predicate``
842    Decorator that creates a predicate out of any callable:
843
844    .. code:: python
845
846        >>> @predicate
847        ... def is_book_author(user, book):
848        ...     return book.author == user
849        ...
850        >>> is_book_author
851        <Predicate:is_book_author object at 0x10eeaa490>
852
853    Customising the predicate name:
854
855    .. code:: python
856
857        >>> @predicate(name='another_name')
858        ... def is_book_author(user, book):
859        ...     return book.author == user
860        ...
861        >>> is_book_author
862        <Predicate:another_name object at 0x10eeaa490>
863
864    Binding ``self``:
865
866    .. code:: python
867
868        >>> @predicate(bind=True)
869        ... def is_book_author(self, user, book):
870        ...     if 'user_has_special_flag' in self.context:
871        ...         return self.context['user_has_special_flag']
872        ...     return book.author == user
873
874
875Predefined predicates
876---------------------
877
878``always_allow()``, ``always_true()``
879    Always returns ``True``.
880
881``always_deny()``, ``always_false()``
882    Always returns ``False``.
883
884``is_authenticated(user)``
885    Returns the result of calling ``user.is_authenticated()``. Returns
886    ``False`` if the given user does not have an ``is_authenticated`` method.
887
888``is_superuser(user)``
889    Returns the result of calling ``user.is_superuser``. Returns ``False``
890    if the given user does not have an ``is_superuser`` property.
891
892``is_staff(user)``
893    Returns the result of calling ``user.is_staff``. Returns ``False`` if the
894    given user does not have an ``is_staff`` property.
895
896``is_active(user)``
897    Returns the result of calling ``user.is_active``. Returns ``False`` if the
898    given user does not have an ``is_active`` property.
899
900``is_group_member(*groups)``
901    Factory that creates a new predicate that returns ``True`` if the given
902    user is a member of *all* the given groups, ``False`` otherwise.
903
904
905Shortcuts
906---------
907
908Managing the shared rule set
909++++++++++++++++++++++++++++
910
911``add_rule(name, predicate)``
912    Adds a rule to the shared rule set. See ``RuleSet.add_rule``.
913
914``set_rule(name, predicate)``
915    Set the rule with the given name from the shared rule set. See
916    ``RuleSet.set_rule``.
917
918``remove_rule(name)``
919    Remove a rule from the shared rule set. See ``RuleSet.remove_rule``.
920
921``rule_exists(name)``
922    Returns whether a rule exists in the shared rule set. See
923    ``RuleSet.rule_exists``.
924
925``test_rule(name, obj=None, target=None)``
926    Tests the rule with the given name. See ``RuleSet.test_rule``.
927
928
929Managing the permissions rule set
930+++++++++++++++++++++++++++++++++
931
932``add_perm(name, predicate)``
933    Adds a rule to the permissions rule set. See ``RuleSet.add_rule``.
934
935``set_perm(name, predicate)``
936    Replace a rule from the permissions rule set. See ``RuleSet.set_rule``.
937
938``remove_perm(name)``
939    Remove a rule from the permissions rule set. See ``RuleSet.remove_rule``.
940
941``perm_exists(name)``
942    Returns whether a rule exists in the permissions rule set. See
943    ``RuleSet.rule_exists``.
944
945``has_perm(name, user=None, obj=None)``
946    Tests the rule with the given name. See ``RuleSet.test_rule``.
947
948
949Licence
950=======
951
952``django-rules`` is distributed under the MIT licence.
953
954Copyright (c) 2014 Akis Kesoglou
955
956Permission is hereby granted, free of charge, to any person
957obtaining a copy of this software and associated documentation
958files (the "Software"), to deal in the Software without
959restriction, including without limitation the rights to use,
960copy, modify, merge, publish, distribute, sublicense, and/or sell
961copies of the Software, and to permit persons to whom the
962Software is furnished to do so, subject to the following
963conditions:
964
965The above copyright notice and this permission notice shall be
966included in all copies or substantial portions of the Software.
967
968THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
969EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
970OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
971NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
972HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
973WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
974FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
975OTHER DEALINGS IN THE SOFTWARE.
976