8. Policies¶
Everything so far was a permission and a scope, and needed no code to decide. One rule of the shop is not about who is asking, but about the order: nobody edits it once it is delivered. That is a policy:
"""
A rule no permission can say: nobody edits an order once it is delivered.
Everything else - view, create, approve, print an invoice - is decided by the
permission and its scope, with no code.
"""
from clearance.policies import BasePolicy
from clearance.registry import registry
from shop.models import Order
class OrderPolicy(BasePolicy):
model = Order
codename = 'order'
def can_edit(self, user, obj=None):
if not self.has_within_scope(user, 'edit', obj):
return False
return obj is None or obj.status != Order.Status.DELIVERED
order_access = registry.register(OrderPolicy)
- A model with no policy is asked through
ModelPolicy: its permission, within its scope. A policy is written only for a rule no permission can say. - A hook is
can_<action>.has_within_scopeis the permission and the scope - what the model would answer with no policy - and the hook adds its own rule after it. obj=Noneis the question asked of a list, before any row: "may they edit orders at all?"
Register it where it is imported at start-up - the shop imports it in its
app's ready():
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ShopConfig(AppConfig):
name = 'shop'
verbose_name = _('Shop')
default_auto_field = 'django.db.models.BigAutoField'
def ready(self) -> None:
from shop import policies # noqa: F401 - registers the order policy
Asking it¶
Every door asks the policy - the admin site, Django's views and the API - and so can a project:
from shop.policies import order_access
order_access.policy.allows(user, 'edit', order)
order_access.policy.authorize(user, 'edit', order) # PermissionDenied if not
south_manager holds order_edit in their branch, and the rug is South's -
but it is delivered, so they may not edit it, in every door.