django-rules Documentation

repository·master·Indexed 24 days ago

https://github.com/dfunckt/django-rules

A lightweight framework for building rule-based permission systems in Django. It provides object-level permissions using predicates and rule sets without requiring database storage for the rules. Features include logical operators for combining predicates, integration with Django Admin, Django Rest Framework, and templates, as well as support for declaring permissions directly within model Meta classes.

Tokens
4.5K
Snippets
21
Records
25
Agent score
34%

What's inside django-rules

  1. Use Predicate.context to share data between predicates

    master

    When Predicate.test() is invoked, a temporary context (a simple dict) is created. This context is valid only for the duration of that specific invocation. You can use it to cache expensive computations or set flags that subsequent predicates in the same chain can access.

    Predicate.context also provides an args attribute containing the original arguments passed to test().

    >>> @predicate
    ... def mypred(a, b):
    ...     value = compute_expensive_value(a)
    ...     mypred.context['value'] = value
    ...     return True
    
    >>> @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()
  2. Combine predicates with logical operators

    master

    Predicates can be combined using binary operators to create complex logic. This allows you to build sophisticated permission requirements from simple building blocks.

    Supported operators:

    • P1 & P2: AND (Returns True if both are true. If P1 is False, P2 is not evaluated).
    • P1 | P2: OR (Returns True if either is true. If P1 is True, P2 is not evaluated).
    • P1 ^ P2: XOR (Returns True if exactly one is true).
    • ~P: NOT (Negates the result of the predicate).
    # Example: Author OR Editor
    is_editor = rules.is_group_member('editors')
    is_book_author_or_editor = is_book_author | is_editor
    
    # Update an existing rule with the combined predicate
    rules.set_rule('can_edit_book', is_book_author_or_editor)
  3. Set up and test rules

    master

    A rule maps a unique identifier (e.g., 'can_edit_book') to a predicate. Rules are managed within a RuleSet. You can add rules to the default rule set using rules.add_rule() and test them using rules.test_rule(rule_name, user, obj).

    # Define a predicate
    @rules.predicate
    def is_book_author(user, book):
        return book.author == user
    
    # Add rules to the rule set
    rules.add_rule('can_edit_book', is_book_author)
    rules.add_rule('can_delete_book', is_book_author)
    
    # Test the rules
    # returns True if the user is the author
    rules.test_rule('can_edit_book', adrian, guidetodjango)
  4. Enable debug logging for predicate evaluation

    master

    To debug predicate evaluation, configure the rules logger at the DEBUG level. This will print a log message for each individual predicate when it is evaluated. If using Django, add this configuration to your settings.py.

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

    master

    To enable object-level permissions, you must add the rules.permissions.ObjectPermissionBackend to your Django AUTHENTICATION_BACKENDS setting. It is typically placed before the default ModelBackend.

    Note: Calling has_perm on a superuser will always return True regardless of these settings.

    AUTHENTICATION_BACKENDS = (
        'rules.permissions.ObjectPermissionBackend',
        'django.contrib.auth.backends.ModelBackend',
    )
  6. Configure rules in Django

    master

    To integrate rules with Django, you must add it to your INSTALLED_APPS and configure the AUTHENTICATION_BACKENDS to include rules.permissions.ObjectPermissionBackend. This allows Django to use rules for object-level permissions.

    INSTALLED_APPS = (
        # ...
        'rules',
    )
    
    AUTHENTICATION_BACKENDS = (
        'rules.permissions.ObjectPermissionBackend',
        'django.contrib.auth.backends.ModelBackend',
    )
  7. Declare object-level permissions in Models

    master

    You can declare permissions directly within a model's Meta class using the rules_permissions dictionary. This requires switching your model's base or metaclass to the extensions provided in rules.contrib.models.

    Implementation Options

    • Standard Model: Use rules.contrib.models.RulesModel as the base class.
    • Custom Base Class: Add rules.contrib.models.RulesModelMixin to your inheritance list and set rules.contrib.models.RulesModelBase as the metaclass.
    • Custom Metaclass: Inherit from rules.contrib.models.RulesModelBaseMixin.

    Example using RulesModel

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

    Programmatic Permission Retrieval

    Use the get_perm classmethod from RulesModelMixin to convert a permission type to its full name:

    if user.has_perm(Book.get_perm("read")):
        ...
  8. Configure Autodiscovery for rules.py modules

    master

    To avoid manual imports and potential circular dependencies, you can configure rules to automatically discover rules.py modules within your Django apps. Add rules.apps.AutodiscoverRulesConfig to your INSTALLED_APPS setting.

    INSTALLED_APPS = (
        # replace 'rules' with:
        'rules.apps.AutodiscoverRulesConfig',
    )
  9. Use rules and permissions in Django Templates

    master

    To use rules in templates, add 'rules' to your INSTALLED_APPS and load the tags using {% load rules %}.

    • {% has_perm 'app.perm' object as var %}: Checks if a user has a specific permission for a given object.
    • {% test_rule 'rule_name' user as var %}: Tests a specific rule against a user.
    {% 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 %}
  10. Enable object-level permissions in Django Admin

    master

    By default, Django Admin only checks model-level permissions. To enable object-level permission checking in the Admin, use rules.contrib.admin.ObjectPermissionsModelAdmin as the base class for your ModelAdmin.

    This allows you to define rules for specific actions like add, view, change, and delete on specific instances.

    # 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)
  11. Create dynamic predicates

    master

    You can create predicates dynamically by wrapping the @rules.predicate decorator inside a factory function. This allows you to inject parameters into the predicate logic.

    import rules
    
    
    def role_is(role_id):
        @rules.predicate
        def user_has_role(user):
            return user.role.id == role_id
    
        return user_has_role
    
    
    rules.add_perm("reports.view_report_abc", role_is(12))
    rules.add_perm("reports.view_report_xyz", role_is(13))