Object Level Permissions - Django REST Framework
Last Updated :
27 Oct, 2025
Object-level permissions in Django REST Framework (DRF) allow you to control access to individual model instances. While standard permissions check whether a user can access a view, object-level permissions determine whether a user can perform actions on a specific object.
To implement custom object-level permissions, create a class that inherits from rest_framework.permissions.BasePermission and override one or both of the following methods:
- has_permission(self, request, view): Checks general access at the view level.
- has_object_permission(self, request, view, obj): Checks access for a specific object instance.
Even if general permissions are set on a model, any authenticated user might still be able to modify or delete objects. Custom object-level permissions ensure that only the owner of an object can update or delete it, preventing unauthorized actions on individual records
Creating customized permission class
Consider a project named 'robot_project' having an app named 'robots'. Inside the robots folder, create a new file named custompermission.py:
Python
from rest_framework import permissions
class IsCurrentUserOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
# The method is a safe method
return True
else:
# The method isn't a safe method
# Only owners are granted permissions for unsafe methods
return obj.owner == request.user
IsCurrentUserOwnerOrReadOnly class inherits from BasePermission and overrides has_object_permission. Safe methods are allowed for all users, while unsafe methods are restricted to the object owner.
Updating the Robot Model
Add the owner field to the robots/models.py file.
owner = models.ForeignKey(
'auth.User',
related_name= 'robots',
on_delete=models.CASCADE
)
Complete Robot model:
Python
class Robot(models.Model):
CURRENCY_CHOICES = (
('INR', 'Indian Rupee'),
('USD', 'US Dollar'),
('EUR', 'Euro'),
)
name = models.CharField(max_length=150, unique=True)
robot_category = models.ForeignKey(
RobotCategory,
related_name='robots',
on_delete=models.CASCADE)
manufacturer = models.ForeignKey(
Manufacturer,
related_name='robots',
on_delete=models.CASCADE)
currency = models.CharField(
max_length=3,
choices=CURRENCY_CHOICES,
default='INR')
price = models.IntegerField()
manufacturing_date = models.DateTimeField()
owner = models.ForeignKey(
'auth.User',
related_name='robots',
on_delete=models.CASCADE
)
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
models.CASCADE ensures that deleting a user also deletes associated robots.
Updating the Serializer
Add the owner field in robots/serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
Full RobotSerializer:
Python
class RobotSerializer(serializers.HyperlinkedModelSerializer):
robot_category = serializers.SlugRelatedField(
queryset=RobotCategory.objects.all(), slug_field='name')
manufacturer = serializers.SlugRelatedField(
queryset=Manufacturer.objects.all(), slug_field='name')
currency = serializers.ChoiceField(choices=Robot.CURRENCY_CHOICES)
currency_name = serializers.CharField(
source='get_currency_display',
read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Robot
fields = '__all__'
User Serializers
Python
class UserRobotSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Robot
fields = ('url', 'name')
class UserSerializer(serializers.HyperlinkedModelSerializer):
robots = UserRobotSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ('url', 'pk', 'username', 'robots')
UserRobotSerializer serializes a user's robots with minimal fields. UserSerializer nests the robots under the user.
Override perform_create in RobotList:
Python
class RobotList(generics.ListCreateAPIView):
queryset = Robot.objects.all()
serializer_class = RobotSerializer
name = 'robot-list'
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
The perform_create method passes the owner information to the create method using the serializer.save method.
A new owner field has been added to the Robot table. Run the migrations to apply these changes to the database. For existing robot records, a default owner must be assigned by noting the ID of an existing user and providing it during the migration. The user ID can be found using the Django shell:

Migrations:

Run the "python manage.py migrate" command to apply the generated migrations.
Setting Authentication and Permissions
Add Basic Authentication in settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':(
'rest_framework.authentication.BasicAuthentication',
)
}
Update views to include custom permissions:
Python
from rest_framework import permissions
from robots import custompermission
class RobotList(generics.ListCreateAPIView):
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
custompermission.IsCurrentUserOwnerOrReadOnly,
)
queryset = Robot.objects.all()
serializer_class = RobotSerializer
name = 'robot-list'
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class RobotDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
custompermission.IsCurrentUserOwnerOrReadOnly,
)
queryset = Robot.objects.all()
serializer_class = RobotSerializer
name = 'robot-detail'
Testing HTTP Requests
Retrieve robots (safe method, no authentication needed):
http :8000/robot/
Output:
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Language: en
Content-Length: 2116
Content-Type: application/json
Date: Sun, 29 Aug 2021 07:11:39 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Accept-Language
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
[
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2019-10-12T00:00:00Z",
"name": "FANUC M-710ic/50",
"owner": "sonu",
"price": 37000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/1/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "ABB",
"manufacturing_date": "2020-05-10T00:00:00Z",
"name": "IRB 1100",
"owner": "sonu",
"price": 25000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/7/"
},
]
Delete a robot (only owner allowed):
Superuser:
http -a "admin":"admin@123" DELETE :8000/robot/1/
Output:

Owner:
http -a "sonu":"sn@pswrd" DELETE :8000/robot/1/
Output:

Output shows the robot is deleted only when the authenticated user is the owner.
Explore
Python Fundamentals
Python Data Structures
Advanced Python
Data Science with Python
Web Development with Python
Python Practice