How to use get_object() in DRF generics views: examples

Katarzyna Antonik-Heller
4 min readMay 19, 2023

--

Why there are two approaches for writing detail views based on generics in Django REST Framework? Which should be considered as correct?

Writing views based on generics in REST Framework

At the beginning, let’s sum up what kind of views do we have in rest_framework.generics:

#1 For a single model instance

  • RetrieveAPIView,
  • DestroyAPIView,
  • UpdateAPIView,
  • RetrieveUpdateAPIView,
  • RetrieveDestroyAPIView,
  • RetrieveUpdateDestroyAPIView.

#2 For the collection of model instances

  • ListAPIView,
  • ListCreateAPIView,

Common sense tells there is a simple case. For ListViews we need to retrieve all instances, and for DetailView a single instance. And that’s THE part that confuses me. But before that, here is how you can write views that extends rest_framework.generics classes for a single model instance.

Writing generics in Django REST Framework

I found it confusing for one reason. There are two different approaches, and I don’t know which should be considered as correct.

#1 Approach: retrieving whole queryset from db

from rest_framework import generics, permissions
from .models import YourModel
from .serializers import YourSerializer

class YourDetailView(generics.RetrieveAPIView):
queryset = YourModel.objects.all()
serializer_class = YourSerializer
permission_classes = [YourCustomPermission]

But… Should I retrieve all instances from a database even if I am writing a view for a single model instance?

Won’t that lead to performance decrease while retrieving hundreds or thousands of data?

Is it a good practice?

#2 Approach: overriding get_object() method

Another approach (which seems more accurate for me) is with retrieving single instance. That can be done by using get_object method.

In first example, it calls the super().get_object() method to retrieve the object, and then calls self.check_object_permissions(self.request, obj) to check the permissions for the object. Finally, it returns the object.

from rest_framework import generics, permissions
from .models import YourModel
from .serializers import YourSerializer

class YourDetailView(generics.RetrieveAPIView):
queryset = YourModel.objects.all()
serializer_class = YourSerializer
permission_classes = [YourCustomPermission]

def get_object(self):
obj = super().get_object()
self.check_object_permissions(self.request, obj)
return obj

In second example, it first retrieves the object based identified on provided data, attribute or key argument (e.g. pk from URL parameter), and then calls self.check_object_permissions(self.request, obj) to check the permissions for the object. Finally, it returns the object.

from rest_framework import generics, permissions
from .models import YourModel
from .serializers import YourSerializer

class YourDetailView(generics.RetrieveAPIView):
serializer_class = YourSerializer
permission_classes = [YourCustomPermission]

def get_object(self):
obj = YourModel.objects.get(pk = self.kwargs['pk'])
self.check_object_permissions(self.request, obj)
return obj

To make it even more elegant, we can add exception handling.

from rest_framework import generics, permissions
from .models import YourModel
from .serializers import YourSerializer

class YourDetailView(generics.RetrieveAPIView):
serializer_class = YourSerializer
permission_classes = [YourCustomPermission]

def get_object(self):
try:
obj = YourModel.objects.get(pk = self.kwargs['pk'])
self.check_object_permissions(self.request, obj)
return obj
except ObjectDoesNotExist:
raise Http404

In both methods (approaches #1 and #2) we set serializer and permission classes the same way, and then can choose between retrieving whole queryset and overriding get_object method.

BUT! There is a trap…

get_object() trap

I decided to hold with get_object method but I didn’t pay enough attention. That cost me few hours considering why tests are not giving expected results (even if on development server it worked correctly). The reason is…

In Django REST Framework, if you override the get_object() method in a generic view, the permissions specified in the class's permission_classes attribute WILL NOT be automatically enforced.

It means my code couldn’t be executed as expected because any permission was taken into consideration. There is NO PERMISSION CHECK for the object!

class GroupDetailView(generics.RetrieveAPIView):
permission_classes = [GroupDetailViewForRelatedTeacher]
serializer_class = GroupSerializer

def get_object(self):
return Group.objects.get(pk=self.kwargs['pk'])

In this error prone example, theget_objectmethod is overridden without including check_object_permissions part. It means that, even if I was expecting the GroupDetailViewForRelatedTeacher custom permission to perform, the view was accessible for every teacher:

class GroupDetailViewForRelatedTeacher(permissions.BasePermission):

def has_object_permission(self, request, view, obj):
if request.user.is_superuser or obj.teacher.user.email == request.user.email:
return True
else:
return False

As mentioned above. If you decide to use get_object method, it is your responsibility to add permission check. It won’t be done automatically.

So, here is how updated code should look like:

class GroupDetailView(generics.RetrieveAPIView):
permission_classes = [GroupDetailViewForRelatedTeacher]
serializer_class = GroupSerializer

def get_object(self):
obj = Group.objects.get(pk=self.kwargs['pk'])
self.check_object_permissions(self.request, obj)
return obj

The get_object() method retrieves the Group instance based on the provided primary key (pk) in the URL parameter.

urlpatterns = [
...
path("group/<int:pk>/", GroupDetailView.as_view(), name="group_detail"),
...
]

Permission check in generic views

In this article I tried to sum up how detail views can be written by extending rest_framework.generics. As you saw, there are two approaches:

  • retrieving queryset from database (queryset = YourModel.objects.all())
  • or retrieving single instance (with get_object method).

Second approach can be tricky. Remember if you override the get_object() method, YOU take control of how the object is retrieved, which means the default permission checks provided by the generic view are bypassed. It becomes your responsibility to handle permissions explicitly within the get_object() method or any other relevant methods.

Hope it helps!

*Source code with mentioned: generic views, permissions, tests.

--

--