Skip to main content

Django Admin Permissions

Introduction

Django's admin interface is a powerful tool that allows developers to manage application data through a user-friendly interface. However, not all users should have the same level of access to your application's data. This is where Django's permission system comes into play, providing a secure way to control who can view, add, change, or delete data in your admin interface.

In this tutorial, we'll explore Django's permission system in depth, learning how to set up different permission levels, customize permissions for specific user groups, and implement practical permission strategies for real-world applications.

Understanding Django's Permission System

Django includes a built-in permission system that integrates seamlessly with the admin interface. At its core, the system creates four basic permissions for each model in your application:

  • add: Permission to add or create new instances of the model
  • change: Permission to modify existing instances of the model
  • delete: Permission to remove instances of the model
  • view: Permission to view instances of the model (added in Django 2.1)

These permissions are automatically created when you run migrations for your models, following a naming pattern of app_label.action_modelname. For example, for a Book model in an app called library, Django would create:

  • library.add_book
  • library.change_book
  • library.delete_book
  • library.view_book

Setting Up User Permissions

Let's start by understanding how to assign permissions to users in Django admin:

Step 1: Create a Superuser

If you haven't already, create a superuser who has full admin access:

bash
python manage.py createsuperuser

Step 2: Create Regular Users

Next, let's create regular users and assign specific permissions to them. Navigate to your admin interface (usually at http://127.0.0.1:8000/admin/), login with your superuser account, and go to "Users" under the "Authentication and Authorization" section.

Click "Add User" to create a new user:

  1. Enter username and password
  2. Click "Save and continue editing"
  3. On the next page, you can fill out additional information

Step 3: Assign Permissions

Now you can assign specific permissions to the user:

  1. Scroll down to the "Permissions" section
  2. In the "User permissions" box, you can search and select specific permissions
  3. Click the right arrow to assign selected permissions to the user
  4. Save the user

Working with Groups for Easier Permission Management

For larger applications with multiple users who need the same permissions, Django provides Groups. Groups are collections of permissions that can be assigned to users.

Creating a Group with Specific Permissions

  1. In the admin interface, go to "Groups" under "Authentication and Authorization"
  2. Click "Add Group"
  3. Name your group (e.g., "Content Editors")
  4. Select the permissions you want to assign to this group
  5. Click "Save"

Now you can add users to this group instead of assigning the same permissions repeatedly:

python
from django.contrib.auth.models import User, Group

# Get the user and group
user = User.objects.get(username='editor_user')
editors_group = Group.objects.get(name='Content Editors')

# Add the user to the group
user.groups.add(editors_group)
user.save()

Custom Model Admin Permissions

Django allows you to customize permissions at the model admin level, giving you fine-grained control over what actions users can perform.

Restricting Model Admin Access

Let's say we have a Book model and we want to customize permissions in the admin:

python
# models.py
from django.db import models

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
publication_date = models.DateField()
is_bestseller = models.BooleanField(default=False)

def __str__(self):
return self.title
python
# admin.py
from django.contrib import admin
from .models import Book

class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'publication_date', 'is_bestseller')

# Control who can access the model in admin
def has_module_permission(self, request):
# Only allow staff with specific permissions
return request.user.is_staff and request.user.has_perm('library.view_book')

# Control who can view the list of books
def has_view_permission(self, request, obj=None):
return request.user.has_perm('library.view_book')

# Control who can add books
def has_add_permission(self, request):
return request.user.has_perm('library.add_book')

# Control who can change books
def has_change_permission(self, request, obj=None):
# Basic permission check
if not request.user.has_perm('library.change_book'):
return False

# Additional custom logic - only authors can edit their own books
if obj is not None and hasattr(request.user, 'author_profile'):
return obj.author == request.user.author_profile.name
return True

# Control who can delete books
def has_delete_permission(self, request, obj=None):
# Only superusers can delete books
return request.user.is_superuser

admin.site.register(Book, BookAdmin)

Field-Level Permissions with ModelAdmin

You can also control which fields a user can edit by overriding the get_readonly_fields method:

python
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'publication_date', 'is_bestseller')

def get_readonly_fields(self, request, obj=None):
# Default readonly fields
readonly_fields = []

# If not superuser, make 'is_bestseller' read-only
if not request.user.is_superuser:
readonly_fields.append('is_bestseller')

# If editing an existing object and user is not in marketing group
if obj is not None and not request.user.groups.filter(name='Marketing').exists():
readonly_fields.append('publication_date')

return readonly_fields

Limiting QuerySets Based on User Permissions

Sometimes you want users to only see and manage specific records. You can override the get_queryset method:

python
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'publication_date', 'is_bestseller')

def get_queryset(self, request):
qs = super().get_queryset(request)

# Superusers can see all books
if request.user.is_superuser:
return qs

# Authors can only see their own books
if hasattr(request.user, 'author_profile'):
return qs.filter(author=request.user.author_profile.name)

# Editors can see all non-bestseller books
if request.user.groups.filter(name='Editors').exists():
return qs.filter(is_bestseller=False)

# Marketing team can only see bestsellers
if request.user.groups.filter(name='Marketing').exists():
return qs.filter(is_bestseller=True)

# Default: return empty queryset if no conditions met
return qs.none()

Creating Custom Permissions

Besides the default permissions, you can create custom permissions for specific business needs:

python
# models.py
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
publication_date = models.DateField()
is_bestseller = models.BooleanField(default=False)

class Meta:
permissions = [
("can_mark_bestseller", "Can mark books as bestsellers"),
("can_view_sales_data", "Can view book sales data"),
]

After adding these custom permissions, run migrations:

bash
python manage.py makemigrations
python manage.py migrate

Now you can use these permissions in your views or admin:

python
# Check if user has custom permission
if request.user.has_perm('library.can_mark_bestseller'):
# Allow user to mark book as bestseller

Practical Example: A Book Publisher Application

Let's create a complete example of a book publisher application with different user roles:

  1. Admins: Full access to everything
  2. Editors: Can view and edit book content but not delete or change publication status
  3. Marketing: Can view books and update bestseller status
  4. Authors: Can only view and edit their own books
python
# models.py
from django.db import models
from django.contrib.auth.models import User

class Author(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='author_profile')
name = models.CharField(max_length=100)
bio = models.TextField()

def __str__(self):
return self.name

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
content = models.TextField()
publication_date = models.DateField(null=True, blank=True)
is_published = models.BooleanField(default=False)
is_bestseller = models.BooleanField(default=False)
sales_count = models.IntegerField(default=0)

class Meta:
permissions = [
("can_mark_bestseller", "Can mark books as bestsellers"),
("can_publish", "Can publish books"),
("can_view_sales", "Can view book sales data"),
]

def __str__(self):
return self.title
python
# admin.py
from django.contrib import admin
from .models import Author, Book

class AuthorAdmin(admin.ModelAdmin):
list_display = ('name', 'user')

def get_queryset(self, request):
qs = super().get_queryset(request)
# Authors can only see themselves
if not request.user.is_superuser and hasattr(request.user, 'author_profile'):
return qs.filter(user=request.user)
return qs

class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'publication_date', 'is_published', 'is_bestseller', 'sales_count')
list_filter = ('is_published', 'is_bestseller')
search_fields = ('title', 'author__name')

fieldsets = (
(None, {
'fields': ('title', 'author', 'content')
}),
('Publication Info', {
'fields': ('publication_date', 'is_published')
}),
('Marketing Data', {
'fields': ('is_bestseller', 'sales_count'),
}),
)

def get_queryset(self, request):
qs = super().get_queryset(request)

# Superusers see everything
if request.user.is_superuser:
return qs

# Authors see only their books
if hasattr(request.user, 'author_profile'):
return qs.filter(author=request.user.author_profile)

return qs

def get_readonly_fields(self, request, obj=None):
readonly_fields = []

# Only those with publish permission can change publication status
if not request.user.has_perm('library.can_publish'):
readonly_fields.extend(['is_published', 'publication_date'])

# Only those with bestseller permission can change bestseller status
if not request.user.has_perm('library.can_mark_bestseller'):
readonly_fields.append('is_bestseller')

# Only superusers can see and edit sales count
if not request.user.has_perm('library.can_view_sales'):
readonly_fields.append('sales_count')

return readonly_fields

def has_delete_permission(self, request, obj=None):
# Only superusers and editors can delete
return request.user.is_superuser or request.user.groups.filter(name='Editors').exists()

admin.site.register(Author, AuthorAdmin)
admin.site.register(Book, BookAdmin)

Setting up the Groups and Permissions

Here's how to set up the necessary groups and permissions programmatically:

python
# management/commands/setup_permission_groups.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from library.models import Book, Author

class Command(BaseCommand):
help = 'Creates permission groups for book publisher system'

def handle(self, *args, **options):
# Create groups if they don't exist
editors_group, created = Group.objects.get_or_create(name='Editors')
marketing_group, created = Group.objects.get_or_create(name='Marketing')
authors_group, created = Group.objects.get_or_create(name='Authors')

# Get content types
book_content_type = ContentType.objects.get_for_model(Book)
author_content_type = ContentType.objects.get_for_model(Author)

# Get permissions
view_book = Permission.objects.get(content_type=book_content_type, codename='view_book')
add_book = Permission.objects.get(content_type=book_content_type, codename='add_book')
change_book = Permission.objects.get(content_type=book_content_type, codename='change_book')
delete_book = Permission.objects.get(content_type=book_content_type, codename='delete_book')

can_publish = Permission.objects.get(content_type=book_content_type, codename='can_publish')
can_mark_bestseller = Permission.objects.get(content_type=book_content_type, codename='can_mark_bestseller')
can_view_sales = Permission.objects.get(content_type=book_content_type, codename='can_view_sales')

# Assign permissions to editors
editors_group.permissions.add(view_book, add_book, change_book, delete_book, can_publish)

# Assign permissions to marketing
marketing_group.permissions.add(view_book, can_mark_bestseller, can_view_sales)

# Assign permissions to authors
authors_group.permissions.add(view_book, change_book)

self.stdout.write(self.style.SUCCESS('Successfully created permission groups'))

Run this command to create the groups:

bash
python manage.py setup_permission_groups

Testing Permissions in Templates

In your Django templates, you can check for permissions using template tags:

html
{% if perms.library.view_book %}
<a href="{% url 'admin:library_book_changelist' %}">View Books</a>
{% endif %}

{% if perms.library.can_mark_bestseller %}
<a href="{% url 'mark_bestseller' book.id %}">Mark as Bestseller</a>
{% endif %}

Summary

Django's admin permission system provides a flexible and robust way to control access to your application's data and functionality. In this tutorial, we've learned how to:

  1. Understand Django's built-in permission system
  2. Create users and assign permissions
  3. Use groups for more efficient permission management
  4. Customize model admin permissions with methods like has_view_permission, has_change_permission, etc.
  5. Implement field-level permissions using get_readonly_fields
  6. Filter querysets based on user permissions
  7. Create custom permissions for specific business needs
  8. Build a comprehensive permission structure for a real-world application

By properly implementing permissions in your Django admin, you ensure that users have access only to the data and functionality they need, enhancing both security and user experience.

Further Resources and Exercises

Additional Resources

Exercises

  1. Basic Permission Setup Create a simple blog application with User, Post, and Comment models. Set up appropriate permissions so that:

    • Regular users can view all posts but edit only their own
    • Editors can edit all posts but not delete them
    • Admins have full access
  2. Custom Permission Practice Add custom permissions to your blog application that allow certain users to:

    • Feature posts on the homepage
    • Close comments on posts
    • Mark posts as mature content
  3. Permission Debugging Create a view that shows all permissions a given user has, grouped by model. This can be helpful for debugging permission issues in larger applications.

  4. Advanced: Object-Level Permissions Implement object-level permissions using django-guardian to allow sharing a private post with specific users without making it public.

By mastering Django's permission system, you'll be able to create secure and well-organized admin interfaces that scale with your application's complexity.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)