Django Policies is a flexible, code-based permissions backend for Django, inspired by Laravel's policies. Instead of managing object-level permissions via the database, Django Policies lets you define rules using simple Python methods, making your authorization logic easier to test, maintain, and reason about.
@register(Article)
class ArticlePolicy(Policy):
def can_change(self, user, article):
if user.is_staff:
return True
return user == article.authorpip install django-policiesAdd django_policies to INSTALLED_APPS and add backend to AUTHENTICATION_BACKENDS:
# settings.py snippet
INSTALLED_APPS = [
...
"django_policies",
]
AUTHENTICATION_BACKENDS = [
"django_policies.backends.PolicyBackend",
"django.contrib.auth.backends.ModelBackend",
]Register your first policy in policies.py in your application. Django Policies
will automatically discover policies.
from django_policies.decorators import register
from django_policies.policies import Policy
...
@register(Article)
class ArticlePolicy(Policy):
def can_change(self, user, obj):
return user == obj.authorWhen a policy is registered, all permission checks for that model will be
handled by the registered policy. By default, 4 permissions are registered for
the model (add_<model>, change_<model>, delete_<model>, view_<model>)
and more can be added using permissions
in the model's meta.
For every permission, two methods on the policy class are considered (in this order, first that is found will be called):
- full permission name:
can_change_article - last part of the permission name dropped:
can_change
Permissions that are not defined in the policy will not be handled by Policies, but can still be handled by other authentication backends.
Sometimes, you may need to check if a user has permission without an object instance. For example when you don't have an instance available (creating a new one), or when you want to know if a user has a permission in general.
When a permission check is initiated without an object instance, Django Policies
will consult can_<perm>_some(user) method instead of can_<perm>(user, object):
class ArticlePolicy(Policy):
def can_change(self, user, object):
# Called when an object instance is provided.
...
def can_change_some(self, user):
# Called when an object instance is not provided.
...The _some prefix can be configured using no_object_suffix property on the
policy class.
Sometimes, you want to set policy to a constant value:
class ArticlePolicy(Policy):
def can_change(self, user, obj):
return FalseYou can also set can_change to a constant boolean value:
class ArticlePolicy(Policy):
can_change = FalseBy default, your policies will get called for AnonymousUser, so you will need to handle their permissions as well.
def can_change(self, user, obj):
if not user.is_authenticated:
return False
...Handling anonymous users in every method can lead to repetitive code. To
simplify this, you can globally deny access for anonymous users in a policy by
setting deny_anonymous:
class ArticlePolicy(Policy):
deny_anonymous = TrueThe above is equivalent to inserting if not user.is_authenticated: return False to all policy methods.
For some applications, you will find that denying anoymous access should be the default. Django Policies allows you to change the project default in Django settings:
POLICIES_DENY_ANONYMOUS_DEFAULT = TrueInside views, you can use the default Django's has_perm on the user object:
user.has_perm("app.change_article", article) # -> calls can_change
user.has_perm("app.change_article") # -> calls can_change_someIn template code, Django does not provide a way to access object permissions, so a custom template tag is provided:
{% load policies %}
{% has_perm user "app.change_article" article as can_change %}
{% if can_change %}...{% endif %}
{% has_perm user "app.change_article" as can_change %}
{% if can_change %}...{% endif %}
which is the same as {% if perms.app.change_article %}...{% endif %}For generic views, a modified PermissionRequiredMixin is provided:
from django_policies.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, ...):
permission_required = "app.change_article"
def get_permission_object(self):
return self.articleDjango Policies automatically discovers policies.py files in all installed apps.
This behavior can be disabled by changing the POLICIES_AUTODISCOVER setting
to False. Alternatively, a different file can be discovered by changing
POLICIES_AUTODISCOVER_MODULE to another module name.
- django-rules, a different take on object permissions based on combining logic predicates
- django-guardian, also a different take storing object permissions in a database