Skip to content

2. Permissions

A permission is what can be done to a model. Django gives every model four - view_order, add_order, change_order, delete_order - and the library uses them as they are. The shop declares its own instead, named after what is done:

class Order(models.Model):
    class Status(models.TextChoices):
        PENDING = 'pending', _('Pending')
        APPROVED = 'approved', _('Approved')
        REJECTED = 'rejected', _('Rejected')
        DELIVERED = 'delivered', _('Delivered')

    customer = models.ForeignKey(
        Person, on_delete=models.CASCADE, related_name='orders', verbose_name=_('customer')
    )
    branch = models.ForeignKey(
        Branch, on_delete=models.PROTECT, related_name='orders', verbose_name=_('branch')
    )
    item = models.CharField(_('item'), max_length=120)
    status = models.CharField(
        _('status'), max_length=16, choices=Status.choices, default=Status.PENDING
    )
    #: Set by the shop, never by the customer - empty until it is.
    price = models.DecimalField(_('price'), max_digits=10, decimal_places=2, null=True, blank=True)

    objects = OrderQuerySet.as_manager()

    class Meta:
        verbose_name = _('order')
        verbose_name_plural = _('orders')
        default_permissions = ()
        permissions = build_permissions(
            'order',
            _('orders'),
            actions=(*DEFAULT_ACTIONS, 'approve'),
            labels={'approve': _('Can approve or reject %(resource)s')},
            # Only whoever holds shop.order_view_price reads it.
            protected_fields=('price',),
            # Only whoever may approve writes these, and only from pending.
            status_actions={
                'approve': {'field': 'status', 'to': ['approved', 'rejected'], 'from': ['pending']},
            },
        )

    def __str__(self):
        return f'{self.item} ({self.branch})'

build_permissions makes:

permission from
shop.order_view, order_create, order_edit, order_delete DEFAULT_ACTIONS
shop.order_approve the action added beside them
shop.order_view_price, order_edit_price the protected field - step 6

default_permissions = () turns Django's four off, so a model has one set of names and not two; start-up refuses a model that keeps both (E014). The status action is step 7.

permission_for(Order, 'edit') answers either style, so nothing else in the project has to know which a model keeps.

In the admin site

Access, Permissions lists every permission, and who holds it:

Every permission, and who holds it

A permission that exists is given to nobody until somebody gives it: a new one, added in a release, arrives held by nobody.