Hello! In our last lesson, we secured our API endpoints using Django REST Framework's built-in permission classes like IsAuthenticated and IsAdminUser. These are great for broad-stroke rules, but what happens when you need more specific, fine-grained control?
For instance, a common requirement is to allow a user to edit their own posts, but not anyone else's. This kind of logic depends on the specific object being accessed and can't be handled by the generic built-in classes.
This lesson is all about solving that problem. We will learn how to create custom permission classes from scratch. By the end, you will be able to implement sophisticated, object-level authorization rules that precisely match your application's business logic.
Why Custom Permissions?
The built-in permissions check general properties of the user and the request (e.g., "is authenticated?", "is staff?", "is the request method GET?"). Custom permissions allow you to write any logic you need by inspecting the incoming request, the view being accessed, and, crucially, the specific database object in question.
This opens the door to rules like:
- An author can edit their own articles, but no one else's.
- Only users with a "premium" subscription can access certain content.
- A post can only be modified if its status is "draft".
The Anatomy of a Custom Permission
Creating a custom permission in DRF is straightforward. It involves subclassing BasePermission and overriding one or both of its key methods. By convention, these classes are stored in a permissions.py file within your Django app.
The official DRF documentation provides the essential starting point. Let's look at the structure of a custom permission class and the two methods you'll be working with.
Read the section titled Custom permissions. Focus on understanding the purpose of the two main methods: .has_permission(self, request, view): A view-level check that runs on every request to the view. It's used for general checks that don't involve a specific object, like verifying a user's group membership. .has_object_permission(self, request, view, obj): An object-level check. It only runs for detail views (e.g., /api/posts/5/) and after .has_permission() has already returned True. This is where you implement logic that inspects the object itself, like obj.author == request.user. Also, take note of the SAFE_METHODS constant, which is a convenient way to check for read-only requests (GET, OPTIONS, HEAD).
Implementing Your First Custom Permission
Let's put this theory into practice by building one of the most common custom permissions: a rule that allows anyone to view an object, but only allows the object's author to edit or delete it. This is often called an IsOwnerOrReadOnly permission.
The following video provides a step-by-step walkthrough of creating almost exactly this type of permission. It will guide you through creating the class, implementing the logic, applying it to a view, and testing the result.
Django Rest Framework Series - Permissions and Custom Permissions - Part-2
This tutorial from Very Academy will demonstrate building a custom permission to restrict write access to the author of a blog post.
Please watch the following segments carefully: Building the Class (18:49 - 24:33): The presenter creates a PostUserWritePermission class. Pay close attention to how has_object_permission is implemented. It first checks if the request method is in SAFE_METHODS (allowing read access for everyone) and then checks if the object's author matches the request's user (obj.author == request.user). Applying and Testing (24:33 - 30:00): Watch how the new permission class is added to the permission_classes list on a detail view. The presenter then demonstrates how a user who is the author can see edit/delete options, while a user who is not the author cannot.
As you can see, the core logic is surprisingly simple but incredibly powerful. The combination of checking for safe methods and comparing the object's owner to the current user is a fundamental pattern you will use frequently.
Enhancing Custom Permissions
Your permission logic can be as complex as you need. You can check multiple conditions involving user attributes, group memberships, and object properties.
Custom Error Messages
By default, when a permission check fails, DRF returns a generic "You do not have permission to perform this action." message. You can provide a more informative error by adding a message attribute to your permission class.
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
"""
message = "You must be the owner of this object to edit it." # Custom message
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the object.
return obj.owner == request.user
This gives the API consumer much clearer feedback.
Advanced Logic and Combining Permissions
For more complex scenarios, you can create simple, reusable permission classes and then compose them using logical operators (& for AND, | for OR, ~ for NOT). This is an elegant way to manage complex rules without writing monolithic permission classes.
Custom Permission Classes in Django REST Framework
This article from TestDriven.io explores more advanced use cases, including a permission class with multiple checks and how to combine classes with logical operators.
This reading is optional but highly recommended as it showcases powerful techniques: Read the User Properties section under Custom Permission Examples. It shows a class, AuthorAllStaffAllButEditOrReadOnly, that combines several checks (superuser, safe methods, author, staff status) in a clear, logical order. Skim the Combining and Excluding Permission Classes section. This will introduce you to using &, |, and ~ to build complex rules from simple, modular permission classes. This is a very clean approach that aligns well with good programming principles.
Test your understanding!
You are building a project management API. You need to create a custom permission class called CanUpdateTaskStatus. The rule is: a user can only change the status of a Task object if they are a member of the "Managers" group. However, any authenticated user should still be able to view the task.
The Task model has no owner field. Assume the user's group membership can be checked with request.user.groups.filter(name='Managers').exists().
How would you write the CanUpdateTaskStatus permission class?
Show answer
Here is a possible implementation.
# in permissions.py
from rest_framework import permissions
class CanUpdateTaskStatus(permissions.BasePermission):
message = "Only managers can update the task status."
def has_object_permission(self, request, view, obj):
# Allow any authenticated user to view (read-only)
if request.method in permissions.SAFE_METHODS:
return True
# Check if the user is in the 'Managers' group for write operations
return request.user.groups.filter(name='Managers').exists()
Explanation:
- We only need
has_object_permissionbecause the rule depends on the request method, which is best checked when we have the context of a specific object. - The first
ifstatement allows all read-only requests (GET,HEAD,OPTIONS) to pass, fulfilling the requirement that any authenticated user can view the task. - If the method is a write method (like
PUTorPATCH), the code proceeds to the final line, which returnsTrueonly if the user belongs to the "Managers" group.
Conclusion
You now have the tools to implement any authorization logic your application requires. By creating custom permission classes, you've moved beyond the one-size-fits-all approach of built-in classes and can now enforce fine-grained, object-level security rules.
Key Takeaways:
- Custom permissions are created by subclassing
rest_framework.permissions.BasePermission. has_permissionis for view-level checks and runs first.has_object_permissionis for object-level checks (like checking an object's author) and runs only on detail views afterhas_permissionsucceeds.- The
SAFE_METHODSconstant ('GET','HEAD','OPTIONS') is your go-to for distinguishing read-only requests from write requests. - A classic and powerful pattern is
IsOwnerOrReadOnly, which allows public read access but restricts write access to the object's owner.
Preview of the Next Lesson:
In this lesson, we focused on permission classes within DRF that control access to API endpoints. Django itself has a parallel, built-in permission system that's deeply integrated with its models and the admin interface (e.g., permissions like app_label.add_modelname).
In the next lesson, we will explore object-level permissions in more depth, focusing on how Django's own model permissions can be leveraged by DRF. This will allow us to create a unified permission strategy that works consistently across both our API and the Django Admin.