Django REST Framework Permissions
Implement granular permission systems with DRF permission classes and custom permission logic.
Overview
Django REST Framework's permission system provides layered access control, from basic safety checks to object-level granularity. Understanding how permissions compose and evaluate enables building secure APIs that properly handle complex authorization scenarios. DRF evaluates permissions in order, short-circuiting on the first deny. The DefaultRouter and ViewSets automatically provide SAFE_METHODS permissions (GET, HEAD, OPTIONS) while requiring authentication for unsafe methods. Custom permissions can be created by extending BasePermission and implementing has_permission for view-level and has_object_permission for object-level checks. Object-level permissions are evaluated manually in ViewSets using self.check_object_permissions(). DRF doesn't automatically enforce object permissions—ViewSets must explicitly call this for each retrieved object. This design allows flexibility in when object permissions are checked, but forgetting to call it creates security vulnerabilities. Custom permission logic should follow the principle of explicit denial. Default to denying access, requiring explicit grants for each action. Async permission classes can be created for permissions requiring async operations, though most permissions are synchronous checks against the request and model objects. For complex permission requirements, consider using django Guardian for object-level permissions or rules for permission composition. These libraries integrate with DRF's permission system while providing more sophisticated rule definitions than custom permission classes alone.
Code Example
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from guardian.shortcuts import get_perms, assign_perm
from .models import Project, Task, TeamMembership
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed for any authenticated request
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner
return obj.owner == request.user
class IsProjectMember(permissions.BasePermission):
"""
Permission to check project membership
"""
message = 'You must be a member of this project to access it.'
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return obj.is_public or request.user in obj.members.all()
return request.user in obj.members.all()
class IsTaskAssigneeOrProjectMember(permissions.BasePermission):
"""
Tasks can be modified by assignees or project members
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
user = request.user
task_project = obj.project
if task_project.owner == user:
return True
if obj.assignee == user:
return True
return user in task_project.members.all()
class ProjectPermission(permissions.BasePermission):
"""
Granular project permissions based on role
"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
user = request.user
# Public read access
if request.method in permissions.SAFE_METHODS:
return obj.is_public or user in obj.members.all()
# Admin can do anything
if user == obj.owner or user.is_superuser:
return True
# Check role-based permissions
try:
membership = TeamMembership.objects.get(
project=obj,
user=user
)
if request.method == 'DELETE':
return membership.role == 'admin'
if request.method in ['PATCH', 'PUT']:
return membership.role in ['admin', 'editor']
return True
except TeamMembership.DoesNotExist:
return False
class NotificationPermission(permissions.BasePermission):
"""
Users can only see their own notifications
"""
def has_object_permission(self, request, view, obj):
return obj.recipient == request.user
class ProjectViewSet(ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = [permissions.IsAuthenticated, ProjectPermission]
def get_queryset(self):
user = self.request.user
if user.is_superuser:
return Project.objects.all()
return Project.objects.filter(
models.Q(owner=user) |
models.Q(members=user) |
models.Q(is_public=True)
).distinct()
def perform_create(self, serializer):
project = serializer.save(owner=self.request.user)
assign_perm('view_project', self.request.user, project)
assign_perm('change_project', self.request.user, project)
assign_perm('delete_project', self.request.user, project)
class TaskViewSet(ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [
permissions.IsAuthenticated,
IsTaskAssigneeOrProjectMember
]
def get_queryset(self):
user = self.request.user
return Task.objects.filter(
models.Q(project__owner=user) |
models.Q(project__members=user) |
models.Q(assignee=user)
).distinct()More Django Rules
Django REST Framework Serializer Validation
Implement multi-layer validation in DRF serializers for robust API input handling and security.
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.utils import timezone
from .models import Project, T...Django QuerySet Optimization
Use select_related, prefetch_related, and only() to minimize database queries.
from django.db.models import Prefetch, Count, Q
from django.shortcuts import render
from .models import Author, Book, Publisher
# Problematic: N+1 qu...Django Security Best Practices
Implement comprehensive security measures including CSRF, XSS prevention, and SQL injection protection.
from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.http import require_http_methods
from django.views.decorators...