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', 'reader@example.com', 'password')
self.author = User.objects.create_user('author', 'author@example.com', 'password')
self.editor = User.objects.create_user('editor', 'editor@example.com', '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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)