Model permissions, groups, custom permissions, and RBAC
coreintermediateEvery model automatically gets four permissions (add/change/delete/view — codenames like "articles.add_article"), created by Django itself when migrations run. Custom permissions add project-specific ones (e.g. "can_publish_article") declared in a model's Meta.permissions. A Group bundles permissions for reuse: assign "Editor" the publish permission once, add users to "Editor," rather than granting it per-user. Role-based access control (RBAC) is the pattern this whole system implements — Groups act as roles, permissions are what a role can do, and a user's effective permissions are the union of their own direct permissions plus every group they belong to.
Think of it as
Django auto-creates add/change/delete/view for EVERY model specifically so the common case ("can this user edit Articles at all") needs zero custom code — just checking user.has_perm("articles.change_article"). Custom permissions exist because real authorization almost always needs verbs beyond CRUD (publish, approve, refund, export) that have no generic equivalent — Meta.permissions is how a model declares "this business action needs its own permission," created by the same migration machinery as the automatic four. Groups exist purely because "grant these 6 permissions to these 40 users" doesn't scale as 240 individual grants — a Group is a single reusable record that permissions attach to once, and users attach to the group instead of the permissions directly, which is the entire RBAC pattern in miniature: roles (groups) sit between users and permissions so that changing what a "Moderator" can do is one edit, not N.
What we're doing: Add a custom "can_publish_article" permission and grant it to an Editors group, rather than to individual users.
- 5
- permissions is a list of (codename, description) tuples — makemigrations picks this up like any other model change and creates it via a migration.
Why this works: Publishing is a business action with real consequences (making an article publicly visible) distinct from merely "changing" the article — a custom permission lets it be granted independently of the generic change_article permission, so a contributor can edit drafts without being able to publish them.
Granting a new business-action permission to individual users one at a time instead of via a Group
Wrong
Better
What you see: Revoking or changing the publish permission later means updating every individual user's permission set one at a time, with no single place that represents "what an editor can do" — a new hire needs the same manual per-permission grant repeated, and it is easy to miss one.
Why: Individual per-user permission grants and a Group both end up checked the same way by has_perm() — the difference is entirely about MAINTAINING the assignment over time. A Group centralizes "what a role can do" into one editable record; direct per-user grants scatter that same information across every user row, which is the exact N-times-the-work problem RBAC exists to avoid.
- can_publish_article — Meta.permissions
- leads to Editors (Group) (editors.permissions.add(...))
- Editors (Group) — a reusable, named bundle
- leads to 40 users (one grant, many users)
- 40 users — user.groups.add(editors)
The permission layers, narrowest to broadest scope
Together
Remember: Every model auto-gets add/change/delete/view permissions; Meta.permissions declares custom business-action ones. A Group is a reusable, named bundle of Permissions — the RBAC pattern in miniature. has_perm() checks direct permissions AND every group a user belongs to. Model permissions answer "can this user act on this MODEL at all" — never "on which specific instance," which needs a separate object-level check.
See also: object level and resource level authorization · passwords staff and permissions · inlines actions and permissions

