Model or dataset
django-guardian/django-guardian avatar
django-guardian/django-guardian

django-guardian: per-object permissions for Django

Per object permissions for Django

3,917 stars596 forksPythonNOASSERTION

At a glance

What is it?
django-guardian adds row-level permissions on top of Django's authorization backend. It is a small library with a narrow job, and the interesting questions are how it stores grants, what it costs to query them, and when plain model fields would do the same work more cheaply.
Who is it for?
Adopt django-guardian when permissions genuinely vary per row and you want to keep using Django's has_perm and admin. Skip it when a single owner field or a tenant column already answers every access question, because the generic tables and the get_objects_for_user queryset are more machinery than that needs.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem django-guardian solves, and who actually needs it

Django ships with model-level permissions. A user either has change_group or does not, and that answer is the same for every row in the table. The moment a product needs "this user can edit this document but not that one", the built-in permission framework stops being enough, and teams start hand-rolling join tables, owner foreign keys and custom querysets.

django-guardian is that join table, generalised. The README describes it as "an implementation of per-object permissions on top of Django's authorization backend", and the pyproject keywords list object and row level alongside authorization. The intended audience is Django developers who already rely on has_perm, groups and the admin, and who want object-level checks to flow through the same API instead of a parallel one.

The library is not aimed at projects that only need a single owner column. If every access decision reduces to "is this row mine", a ForeignKey to User and a filter is less code and fewer queries than a generic permission table. guardian earns its place when the same object can be granted to several users and groups, when grants are revoked independently, or when an admin needs to inspect and edit them.

How ObjectPermissionBackend and the permission tables fit together

The mechanism is a second authentication backend. Settings gain guardian.backends.ObjectPermissionBackend alongside Django's ModelBackend, and from then on a call like jack.has_perm('change_group', admins) is routed through guardian when an object is supplied. The README example shows the before and after: the same check returns False, then True once assign_perm has created a grant, while jack.has_perm('change_group') without an object still returns False. Object permissions do not leak into global ones.

The storage is generic. The guardian app ships models whose names appear in the README output as UserObjectPermission and GroupObjectPermission, each row binding a user or group, a Django permission and a specific object. Because the target object is referenced generically, guardian works with any model, including third-party ones you cannot edit, at the cost of an extra join per check.

Two entry points matter in practice. assign_perm writes a grant; get_objects_for_user answers the reverse question, which objects may this user act on. The second is the one that shows up in list views, and it is the reason the library is more than a boolean helper. The repository also carries a benchmarks directory, which suggests the maintainers treat query cost as something worth measuring rather than assuming.

Installing django-guardian and assigning your first permission

Installation follows the project's documented flow. The README gives uv add django-guardian and notes that pip install django-guardian works as well.

bash
uv add django-guardian

Configuration is three steps. Add guardian to INSTALLED_APPS, add the backend, then migrate so the permission tables exist.

python
INSTALLED_APPS = (
    ...,
    'guardian',
)

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'guardian.backends.ObjectPermissionBackend',
)
bash
python manage.py migrate

After that, the README's own session is the shortest useful test. It creates a user and a group, checks a permission that is not granted, assigns it with assign_perm, and checks again.

py
from django.contrib.auth.models import User, Group
from guardian.shortcuts import assign_perm

jack = User.objects.create_user('jack', '[email protected]', 'topsecretagentjack')
admins = Group.objects.create(name='admins')
jack.has_perm('change_group', admins)  # False
assign_perm('change_group', jack, obj=admins)
jack.has_perm('change_group', admins)  # True

For the admin, the README says to swap admin.ModelAdmin for guardian.admin.GuardedModelAdmin on the models that need object permissions in the panel. Users of django-unfold get support through a contrib module, per the README.

Where django-guardian gets expensive or wrong

The generic foreign key is the main cost. Every object permission row points at an object by content type and id, so checking a permission on a loaded instance is not a simple column comparison, and listing the objects a user can touch is a query against the permission table joined back to your model. On a table with millions of rows and a handful of grants per object, that query is the part that will show up in your slow query log, not the boolean check.

The second limitation is conceptual. Object permissions are additive grants, not a policy language. There is no built-in notion of deny rules, inheritance down a hierarchy, or conditions such as "only while status is draft". If your rules read like sentences with several clauses, guardian stores the outcome of those rules, and something else has to compute it. Teams that expect the library to express the policy rather than record it end up writing the policy layer anyway.

The README does not document rollback of the migration or a supported path for removing guardian from a project that has accumulated grants. Treat the permission tables as durable state before you enable the backend in production, and check the schema your database actually created rather than assuming the default table names.

django-guardian compared with django-rules

The closest alternative people search for is django-rules, and the difference is where the logic lives. django-guardian stores grants in the database and answers questions by reading them, so permissions can be edited at runtime through the admin or through assign_perm, and the answer depends on data. django-rules evaluates predicate functions written in Python, so the rules live in code, are versioned with the application, and can express conditions that a grant table cannot.

That maps onto different products. A document sharing feature where users decide who sees what is guardian territory: the grants are user data. A workflow where "an editor may publish a post that belongs to their team" is a fixed business rule is closer to django-rules, because the rule is code and nobody needs to edit it per row. Some projects use both, with guardian grants as the input to a predicate.

The practical test is who changes the permission. If the answer is an end user clicking a share button, you want rows in a table. If the answer is a developer in a pull request, you want a function.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-18, days before this writing. Recent releases 3.4.0, 3.4.1 and 3.5.0 landed in August and September 2026, and pyproject.toml carries Development Status 5 - Production/Stable. The declared support matrix is Python 3.10 through 3.14 and Django 3.2 through 6.0, which is wide enough that an upgrade of either Django or Python is unlikely to be blocked by this dependency alone.

Licensing needs a caveat. The repository metadata reports the licence as NOASSERTION, while pyproject.toml declares license = { text = "BSD-2-Clause" } and the classifiers include the BSD licence entry. Those two signals disagree, so if your organisation depends on a specific licence identifier, read the LICENSE file in the repository rather than trusting either the metadata or the classifier. That is a factual check, not legal advice.

Upgrade cost is mostly migration cost. New releases can add or alter guardian's tables, so a project with a large permission table should treat a version bump the same way it treats any schema change: run the migration against a copy, watch the lock time, and confirm the index the library expects is present before pointing production traffic at it.

Editorial conclusion

Adopt django-guardian when permissions genuinely vary per row and you want to keep using Django's has_perm and admin. Skip it when a single owner field or a tenant column already answers every access question, because the generic tables and the get_objects_for_user queryset are more machinery than that needs. Before committing, run the migration on a copy of production data and check the size and index coverage of the guardian permission tables, then verify that your list views use get_objects_for_user rather than filtering after the fact.

Frequently asked questions

What is django-guardian?

It is a Django library that implements per-object permissions on top of Django's authorization backend. It adds an ObjectPermissionBackend and shortcut functions such as assign_perm, so checks like user.has_perm('change_group', obj) can return different answers for different rows.

What is a django-guardian alternative?

django-rules is the usual comparison: it evaluates predicate functions written in Python rather than storing grants in database tables, so its rules live in code and are versioned with the application. guardian keeps permissions as rows that can be edited at runtime, which suits user-driven sharing rather than fixed business rules.

How do I add django-guardian to a Django project?

Install it with uv add django-guardian, add 'guardian' to INSTALLED_APPS, add 'guardian.backends.ObjectPermissionBackend' to AUTHENTICATION_BACKENDS, and run python manage.py migrate to create the permission tables.

How do I list the objects a user has permission on with django-guardian?

The library provides get_objects_for_user for the reverse lookup, returning the objects a user may act on rather than testing one object at a time. The README documents assign_perm and has_perm directly; get_objects_for_user is the entry point used in list views.

Does django-guardian work with the Django admin?

Yes. The README says to replace admin.ModelAdmin with guardian.admin.GuardedModelAdmin for the models that should have object permission support in the admin panel, and notes that django-unfold users get support through a contrib module.

Official sources

  1. django-guardian/django-guardian on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/django-guardian-django-guardian.svg)](https://hysenlabs.com/projects/django-guardian-django-guardian)