Skip to main content

Django Permissions

In web applications, controlling who can do what is crucial for security and proper functionality. Django's permission system provides a robust way to manage access control, allowing you to specify which users can create, view, edit, or delete objects in your application.

Introduction to Django Permissions

Django's permission system is designed to answer a simple question: "Can user X do Y?" The permission system works alongside the authentication system to ensure that authenticated users only access the parts of your application they're authorized to use.

Django provides several built-in permission mechanisms:

  1. Model-level permissions: Control access to models as a whole (e.g., "Can add user", "Can delete post")
  2. Object-level permissions: Fine-grained control over specific instances of a model
  3. View-level permissions: Control access to specific views or pages
  4. Permission groups: Group users and assign permissions to groups rather than individuals

Built-in Model Permissions

By default, Django automatically creates four permissions for each model in your application:

  • add: Can create new objects
  • change: Can modify existing objects
  • delete: Can delete objects
  • view: Can view objects (added in Django 2.1)

These permissions are created when you run makemigrations and migrate commands.

Checking Permissions

You can check if a user has specific permissions using the has_perm method:

python
# Check if a user has permission to add a blog post
if user.has_perm('blog.add_post'):
# Allow the user to add a post
...

The permission string follows the format app_label.permission_codename.

Using Permissions in Views

Django provides several ways to check permissions in your views:

Function-Based Views with Decorators

You can use the @permission_required decorator to protect function-based views:

python
from django.contrib.auth.decorators import permission_required

@permission_required('blog.add_post', login_url='/login/')
def create_post(request):
# Only users with the 'blog.add_post' permission can access this view
# If a user doesn't have permission, they'll be redirected to /login/
...

Class-Based Views with Mixins

For class-based views, Django provides the PermissionRequiredMixin:

python
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import CreateView
from .models import Post

class PostCreateView(PermissionRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
permission_required = 'blog.add_post'
# Optional: Redirect to this URL if permission check fails
login_url = '/login/'
# Optional: If True, redirects to login page; if False, returns a 403 Forbidden error
raise_exception = False

Creating Custom Permissions

Besides the default permissions, you can define custom permissions for your models:

python
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()

class Meta:
permissions = [
("publish_post", "Can publish a post"),
("feature_post", "Can feature a post"),
]

After running migrations, these permissions will be available alongside the default ones.

Permission Groups

Rather than assigning permissions to users individually, you can create groups with specific permission sets and assign users to those groups:

python
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from .models import Post

# Get the content type for the Post model
content_type = ContentType.objects.get_for_model(Post)

# Get permissions for Post model
post_permissions = Permission.objects.filter(content_type=content_type)

# Create an 'Editors' group
editors_group = Group.objects.create(name='Editors')

# Add permissions to the group
editors_group.permissions.set(post_permissions)

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

Object-Level Permissions

Django's built-in permissions work at the model level, meaning they control access to all objects of a model type. For finer control over individual objects, you'll need object-level permissions.

Django doesn't include object-level permissions out of the box, but you can implement them in several ways:

Option 1: Use django-guardian

django-guardian is a popular third-party package for object-level permissions:

bash
pip install django-guardian

Add it to your INSTALLED_APPS:

python
INSTALLED_APPS = [
# ...
'guardian',
# ...
]

AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectPermissionBackend',
]

Using django-guardian:

python
from guardian.shortcuts import assign_perm

# Assign permission to a user for a specific object
assign_perm('change_post', user, post_instance)

# Check if a user has permission for an object
if user.has_perm('change_post', post_instance):
# User can edit this specific post
...

Option 2: Custom Implementation

You can implement your own object-level permissions by overriding the has_perm method in a custom permission backend.

Practical Example: A Blog Application with Different User Roles

Let's build a simple blog application with different user roles:

  1. Readers: Can view published posts
  2. Authors: Can create, edit, and delete their own posts
  3. Editors: Can edit and publish any post
  4. Admins: Full access to all content

Models

python
from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
permissions = [
("publish_post", "Can publish a post"),
("feature_post", "Can feature a post"),
]

Setting Up User Groups and Permissions

python
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from .models import Post

def create_user_groups():
# Get content type for Post model
post_content_type = ContentType.objects.get_for_model(Post)

# Create Author group
author_group, created = Group.objects.get_or_create(name='Authors')
author_permissions = [
Permission.objects.get(content_type=post_content_type, codename='add_post'),
Permission.objects.get(content_type=post_content_type, codename='change_post'),
Permission.objects.get(content_type=post_content_type, codename='delete_post'),
]
author_group.permissions.set(author_permissions)

# Create Editor group
editor_group, created = Group.objects.get_or_create(name='Editors')
editor_permissions = [
Permission.objects.get(content_type=post_content_type, codename='add_post'),
Permission.objects.get(content_type=post_content_type, codename='change_post'),
Permission.objects.get(content_type=post_content_type, codename='delete_post'),
Permission.objects.get(content_type=post_content_type, codename='publish_post'),
]
editor_group.permissions.set(editor_permissions)

Views with Permission Checks

python
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required
from django.http import HttpResponseForbidden
from .models import Post

@login_required
@permission_required('blog.add_post', raise_exception=True)
def create_post(request):
# View implementation...
pass

@login_required
def edit_post(request, post_id):
post = get_object_or_404(Post, id=post_id)

# Check if the user is the author or has edit permission
if request.user == post.author or request.user.has_perm('blog.change_post'):
# View implementation for editing
pass
else:
return HttpResponseForbidden("You don't have permission to edit this post.")

@login_required
@permission_required('blog.publish_post', raise_exception=True)
def publish_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
post.is_published = True
post.save()
return redirect('post_detail', post_id=post.id)

Testing Permissions in Django

Testing permissions is essential to ensure your security system works correctly:

python
from django.test import TestCase
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from .models import Post

class PostPermissionsTestCase(TestCase):
def setUp(self):
# Create test users
self.reader = User.objects.create_user('reader', '[email protected]', 'password')
self.author = User.objects.create_user('author', '[email protected]', 'password')
self.editor = User.objects.create_user('editor', '[email protected]', 'password')

# Create and assign groups
author_group = Group.objects.create(name='Authors')
editor_group = Group.objects.create(name='Editors')

# Get content type for Post model
content_type = ContentType.objects.get_for_model(Post)

# Set up permissions
add_post = Permission.objects.get(content_type=content_type, codename='add_post')
change_post = Permission.objects.get(content_type=content_type, codename='change_post')
publish_post = Permission.objects.create(
codename='publish_post',
name='Can publish a post',
content_type=content_type,
)

# Assign permissions to groups
author_group.permissions.add(add_post, change_post)
editor_group.permissions.add(add_post, change_post, publish_post)

# Assign users to groups
self.author.groups.add(author_group)
self.editor.groups.add(editor_group)

# Create a test post
self.post = Post.objects.create(
title='Test Post',
content='Test content',
author=self.author,
)

def test_author_permissions(self):
self.assertTrue(self.author.has_perm('blog.add_post'))
self.assertTrue(self.author.has_perm('blog.change_post'))
self.assertFalse(self.author.has_perm('blog.publish_post'))

def test_editor_permissions(self):
self.assertTrue(self.editor.has_perm('blog.add_post'))
self.assertTrue(self.editor.has_perm('blog.change_post'))
self.assertTrue(self.editor.has_perm('blog.publish_post'))

def test_reader_permissions(self):
self.assertFalse(self.reader.has_perm('blog.add_post'))
self.assertFalse(self.reader.has_perm('blog.change_post'))
self.assertFalse(self.reader.has_perm('blog.publish_post'))

Best Practices for Django Permissions

  1. Use groups for role-based permissions: Assign permissions to groups rather than individual users for better maintainability.

  2. Keep permissions granular: Create specific permissions for specific actions rather than broad ones.

  3. Check permissions at multiple levels:

    • In views to control page access
    • In templates to show/hide UI elements
    • In API endpoints to secure data
  4. Use permission mixins consistently: Ensure all protected views properly check permissions.

  5. Document your permission system: Keep track of which permissions are required for which features.

Summary

Django's permission system provides a comprehensive framework for controlling access to your application's functionality and data. You can use the built-in model-level permissions, create custom permissions, and implement object-level permissions for more fine-grained control.

Key concepts we covered:

  • Built-in model permissions
  • Custom permissions
  • Permission groups
  • Object-level permissions using django-guardian
  • Testing permissions
  • Best practices for implementing permissions

Exercises

  1. Create a blog application with three user roles: reader, writer, and editor. Implement the appropriate permissions for each role.

  2. Implement object-level permissions to allow users to share their private content with specific other users.

  3. Create a dashboard that shows different content based on the user's permissions.

  4. Add a permission system to an API that restricts access to certain endpoints based on the user's role.

Additional Resources



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