Custom Permissions in Django Rest Framework

Last updated 1 month, 1 week ago | 103 views 75     5

Tags:- Python Django DRF

Permissions in Django Rest Framework (DRF) control who can access what in your APIs. While DRF provides useful built-in permissions (like IsAuthenticated, IsAdminUser, etc.), most real-world applications require custom permission logic.

In this article, you’ll learn:

  • What permissions are in DRF

  • When to create custom permissions

  • How to write them step-by-step

  • Code examples

  • Best practices & common pitfalls


What are Permissions in DRF?

Permissions are checks performed after authentication and before the view is executed. They decide whether the request should proceed.

request.user  # already set by authentication

If a permission fails, DRF raises a 403 Forbidden error by default.


Why Create Custom Permissions?

You need custom permissions when your access control logic goes beyond the default options. For example:

  • Only allow users to edit their own posts

  • Admins can access everything, but regular users have limits

  • Allow read access to everyone, but write access to staff only

  • Restrict deletion to the object owner


How to Write a Custom Permission Class

Step 1: Import Base Class

from rest_framework.permissions import BasePermission

Step 2: Define Your Class

class IsOwner(BasePermission):
    """
    Custom permission to only allow owners of an object to edit or delete it.
    """

    def has_object_permission(self, request, view, obj):
        # Object-level permission
        return obj.owner == request.user

You can override two methods:

Method Description
has_permission(self, request, view) Checks general access before object is retrieved
has_object_permission(self, request, view, obj) Checks object-level access (e.g., to a specific instance)

Example: Owner-Only Post Editing

models.py

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

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    owner = models.ForeignKey(User, on_delete=models.CASCADE)

permissions.py

from rest_framework.permissions import BasePermission

class IsOwnerOrReadOnly(BasePermission):
    """
    Read-only for all, but write access only to owner.
    """

    def has_object_permission(self, request, view, obj):
        if request.method in ('GET', 'HEAD', 'OPTIONS'):
            return True
        return obj.owner == request.user

views.py

from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .permissions import IsOwnerOrReadOnly
from rest_framework.permissions import IsAuthenticatedOrReadOnly

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

serializers.py

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content', 'owner']
        read_only_fields = ['owner']

Combining Permissions

You can use multiple permission classes. DRF treats them like AND conditions: all must pass.

permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

Want custom OR logic? Write a wrapper:

class IsOwnerOrAdmin(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.owner == request.user or request.user.is_staff

✅ Tips & Best Practices

Tip Description
✅ Use has_permission() for global checks e.g., Only allow logged-in users
✅ Use has_object_permission() for instance-level checks e.g., Only the creator can update/delete
✅ Return True/False, not HTTP responses DRF handles the response if permission fails
✅ Use read-only method check To allow safe methods like GET, use request.method in SAFE_METHODS
✅ Document your permission logic Makes debugging and team collaboration easier

⚠️ Common Pitfalls

Pitfall Why It Happens
❌ Modifying response in permission class DRF doesn’t expect that; just return True or False
❌ Forgetting to check request.method Can unintentionally block safe operations like GET
❌ Using has_object_permission without retrieve()/update()/destroy() Those views must fetch the object for the method to trigger
❌ Not using perform_create() in viewsets request.user won’t be assigned to the object

Conclusion

Custom permissions in DRF give you fine-grained control over who can access or modify API resources. By leveraging BasePermission, you can implement business-specific rules cleanly and securely.