ambit
Scoped, multi-dimensional authorization for Django.
Most authorization answers one question: what may this user do? ambit
answers the one that actually shapes real organisations: what, and
where. A department head may edit staff — but only in their department.
A regional manager sees orders — but only in their region. A teacher marks
work — but only their own classes.
Django's built-in permissions are global: hold orders.change_order and you
can change every order. ambit adds the missing axis — scope — without
changing how you write permissions.
has_authorized_scope(user, "orders.change_order", WITHIN_REGION) # anywhere in reach?
authorized_queryset(user, "orders.view_order", Order.objects.all(), WITHIN_REGION) # the rows they may see
can_access_object(user, "orders.change_order", order, WITHIN_REGION) # this exact one?
The idea: the engine is invariant, the dimensions are yours
A dimension is an axis access can be limited along. ambit ships none —
you register the ones your organisation actually has. A school registers
stage / department / classroom / own-teaching; a company registers
region / department / team / own. The scope math, the roles and grants, the
strict-intersection semantics — all identical. The semantics change; the
functionality doesn't.
That's the whole design: the same engine authorises a school and a sales org, because it never knew what a "classroom" or a "region" was — only that it's a dimension with values a grant may constrain.
Install
pip install django-ambit
# settings.py
INSTALLED_APPS = [
# ...
"ambit",
]
python manage.py migrate
Quickstart
1. Register your dimensions (once, e.g. in an AppConfig.ready()):
from ambit.dimensions import register_dimension
# row-backed: a grant points at a Team row
register_dimension("team", "Team", model_label="sales.Team")
# self-resolving: no stored row — resolves to the user's own value at request time
register_dimension("own", "Own records", resolves_to=lambda user: user)
2. Describe how a permission reaches your models — a policy maps each dimension to a lookup path on the object being checked:
from ambit.policies import ScopePolicy, ScopeBinding
WITHIN_REACH = ScopePolicy({
"team": ScopeBinding(queryset_lookup="team"),
"own": ScopeBinding(queryset_lookup="owner"),
})
3. Grant access — a role (a bundle of Django permissions) at a scope (a point in the space your dimensions define):
from ambit.models import Role, RoleAssignment
from ambit.services.scope_builder import scope_for
role = Role.objects.create(name="Rep")
role.permissions.add(view_order_permission)
# "Alice is a Rep in Team North"
RoleAssignment.objects.create(user=alice, role=role, scope=scope_for({"team": north}))
4. Check — anywhere in a view, queryset, or template helper:
from ambit.services.authorization import (
has_authorized_scope, authorized_queryset, can_access_object,
)
authorized_queryset(alice, "sales.view_order", Order.objects.all(), WITHIN_REACH)
# -> only orders in Team North
Or drop the mixins onto class-based views and never write a check by hand:
from ambit.mixins import ScopedQuerysetMixin
class OrderListView(ScopedQuerysetMixin, ListView):
model = Order
permission = "sales.view_order"
authorization_policy = WITHIN_REACH
Concepts
| Concept | What it is |
|---|---|
| Dimension | An axis access is limited along. Row-backed (points at a row, e.g. a Team) or self-resolving (resolves to the user's own value, e.g. "own records"). |
| Scope | A set of per-dimension constraints. Strict intersection: {team: North, own} means in North and their own. A scope with no constraints is global. |
| Policy | Which dimensions a permission is checked against, and the lookup path to each on the target model. |
| Role | A named bundle of Django permissions. |
Grant (RoleAssignment) |
A user holds a role at a scope. |
| Catalog | An optional, human-worded map of the permissions a console should offer (register_catalog). |
Status
ambit is the authorization engine extracted from a production multi-tenant
Django system and generalised to any organisation. It's early (alpha) but the
core is battle-tested. The API may still shift before 1.0.
License
MIT — see LICENSE.
Release files for django-ambit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_ambit-0.1.0.tar.gz | 24.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_ambit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.2 kB
Release files / django_ambit-0.1.0.tar.gz
| Download URL | django_ambit-0.1.0.tar.gz |
|---|---|
| Size | 24.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d595aa45e65e7b9ea574ba208d0862c0c78571c5a4f50f73d71fff2b7e81f9b
|
|
BLAKE2b-256 checksum How to use checksums |
017a42689e34af7ca7c115b9b54679ef593694e0c9a4f88a549ba5a111a43647
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / django_ambit-0.1.0-py3-none-any.whl
| Download URL | django_ambit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
19abb8c511d7c4851b4bd1ef44fb41de4b28eca2dbfaab4058cdac00a2b4fad2
|
|
BLAKE2b-256 checksum How to use checksums |
17c2d5997877d516f79539e0510c7b6052af4366d05ef0dfa648d1ec040d36e9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|