django-guardian lets you grant permissions to individual objects, not just models.

Imagine you have a Project model. With standard Django, you can give a user permission to "change" any Project. With django-guardian, you can give a user permission to "change" only Project with ID 123, or only Project with ID 456. This is incredibly useful for multi-tenant applications or when access control needs to be granular.

Let’s see it in action.

First, install it:

pip install django-guardian

Add it to your INSTALLED_APPS:

# settings.py
INSTALLED_APPS = [
    # ...
    'guardian',
    # ...
]

Now, let’s define a simple model. We’ll need to make it "guardian-aware" by inheriting from guard.mixins.GuardedObjectMixin.

# models.py
from django.db import models
from guardian.mixins import GuardedObjectMixin

class Document(GuardedObjectMixin, models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)

    def __str__(self):
        return self.title

Next, we need to set up the necessary database tables for django-guardian.

python manage.py migrate

Now, let’s grant a permission. We’ll use the assign_perm function. This function takes the permission name, the user, and the specific object.

from django.contrib.auth.models import User
from .models import Document
from guardian.shortcuts import assign_perm

# Assume 'user' is an authenticated User object and 'document' is a Document instance
user = User.objects.get(username='alice')
document = Document.objects.create(title='My Secret Doc', content='Shhh...', owner=user)

# Grant 'change_document' permission to 'alice' for 'My Secret Doc'
assign_perm('change_document', user, document)

To check if a user has permission for a specific object, you use user.has_perm() as usual, but django-guardian hooks into this.

# In a view or elsewhere
from django.shortcuts import get_object_or_404
from .models import Document

def document_detail(request, pk):
    document = get_object_or_404(Document, pk=pk)
    if request.user.has_perm('change_document', document):
        # User can change this document
        return render(request, 'edit_document.html', {'document': document})
    else:
        # User cannot change this document
        return render(request, 'view_document.html', {'document': document})

The system works by creating two new tables: object_permission and user_object_permission. The object_permission table stores pairs of (content type, object ID, permission name), and user_object_permission links users to these specific object permissions. When user.has_perm(perm_name, obj) is called, Django’s permission backend first checks global permissions. If not found, it queries django-guardian’s tables to see if the user has been granted that specific permission for that exact object.

The GuardedObjectMixin on your model is crucial. It adds a get_content_type() method and ensures that django-guardian knows how to identify the object’s type and its primary key for storage in the permission tables. Without it, assign_perm and has_perm checks for objects won’t function correctly.

You can also manage permissions through the Django admin. Make sure your model is registered and then use guardian.admin.GuardedModelAdmin for your model.

# admin.py
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from .models import Document

class DocumentAdmin(GuardedModelAdmin):
    pass

admin.site.register(Document, DocumentAdmin)

This admin integration provides a UI to assign and revoke object-level permissions directly from the Django admin interface, which is invaluable for managing access in production.

The real power comes from combining object-level permissions with group permissions. A user might belong to a group that has change_document permission on the Document model globally, but django-guardian allows you to deny that specific user the ability to change a particular document, overriding the group permission for that one instance. This is achieved by explicitly revoking the permission for the object using remove_perm('change_document', user, document). The has_perm check will then correctly reflect that the user is denied access to that specific document, even if their group has general access.

The next step is to explore how to manage permissions for groups on individual objects, which django-guardian also supports via GroupObjectPermission.

Want structured learning?

Take the full Django course →