This article will discuss some API concepts in Django Rest Framework that make our code shorter and build APIs in Python Django Rest Framework much faster. Below are a few of those concepts:
- Class-based Views (CBVs)
- Generic Views (MIxins and Customization)
- ViewSets
- Routers and Nested Routes
We will explain these concepts with code snippets and build a simple Reviews API that uses these concepts together.
Class Based Views
This concept allows you to move from the usual function-based view to a more convenient and aligned way of writing your Views with inbuilt functions available in the Django rest framework itself
# Class Based View for review api
@api_view(['GET', 'POST'])
def review_list(request):
if request.method == 'GET':
reviews = Review.objects.all()
serializer = ReviewSerializer(reviews, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The code snippet above is a typical function-based concept where you have a view review_list
that has GET and POST properties with the annotations to get and also modify the reviews list, now using the CBVs Concept,
from rest_framework.views import APIView
# Class Based View for review api
class ReviewListAPIView(APIView):
def get(self, request):
reviews = Review.objects.all()
serializer = ReviewSerializer(reviews, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
As you can see in the Code with the help of the APIView it is easier to access the inbuilt function in the APIView which demonstrates the CBVs Concept
Generic Views ( Mixins and Customization)
Unlike class-based views with generic views the boilerplate gets shorter, with the concept of generic view you can see the kind of role you want that view to perform, an example of a simple generic view is down below:
# views.py
from rest_framework.generics import ListCreateAPIView
from .models import Reviews
from .serializers import ReviewSerializer
class ReviewsListView(ListCreateAPIView):
queryset = Reviews.objects.all()
serializer_class = ReviewSerializer
With the code above, you will see the ListCreateAPIView
is a powerful DRF class that handles listing (GET
) and creating (POST
) objects, while other generic views like RetrieveUpdateDestroyAPIView
provide full control over retrieving, updating, and deleting (GET
, PUT/PATCH
, DELETE
).
Customizing this Views
# views.py
from rest_framework.generics import ListCreateAPIView
from .models import Reviews
from .serializers import ReviewSerializer
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size = 10
class ReviewsListView(ListCreateAPIView):
serializer_class = ReviewSerializer
pagination_class = CustomPagination
def get_queryset(self):
return Reviews.objects.filter(rating__gt=3)
In this snippet above you can see that the query set has been modified and also the pagination for this API which would return 10 items per page
Viewsets
Unlike class-based views with generic views the boilerplate gets shorter, with the concept of generic view you can see the kind of role you want that view to perform, an example of a simple generic view is down below:
# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Reviews
from .serializers import ReviewSerializer
class ReviewsViewSet(ModelViewSet):
queryset = Reviews.objects.all()
serializer_class = ReviewSerializer
With the code above, you would see the ModelViewSet which ModelViewSet
is a powerful DRF class that inherits from GenericViewSet
. It combines multiple mixins—Create, Retrieve, Update, Delete, and List—to automate CRUD operations with minimal code.
Customizing this Viewset
class ReviewsViewSet(ModelViewSet):
serializer_class = ReviewSerializer
def get_queryset(self):
"""Return only reviews with a rating above 3"""
return Reviews.objects.filter(rating__gt=3)
In this snippet above you can see that the query set has been modified to get only Reviews with rating of 3 and above which can help business logic along the line for that product that is if its tied to a certain product which can be any sort of relationship .
Routers and Nested Routes
Registering routes for function based view
Understanding how views works for both function based views and class-based views, it is important to know that these views would need to be registered in the routes section to be easily accessed by DRF for use as endpoints for the APIs, in the code snippet below we would be talking about registering your routes for a function based view:
from django.urls import path
from .views import product_list, product_reviews, product_review_detail
urlpatterns = [
path('products/', product_list, name='product-list'),
path('products/<int:product_id>/reviews/', product_reviews, name='product-reviews'),
path('products/<int:product_id>/reviews/<int:review_id>/', product_review_detail, name='product-review-detail'),
]
In the snippet above, The routes are registered manually and act as follows:
GET /products/
→ List all productsGET /products/{product_id}/reviews/
→ List reviews for a specific productPOST /products/{product_id}/reviews/
→ Create a review for a productGET /products/{product_id}/reviews/{review_id}/
→ Retrieve a specific review
Pros and Cons of using this method
Pros:
1️⃣ Fine-grained control – You have full control over request handling, making customization easier.
2️⃣ Lightweight and simple – Less abstraction than ViewSets, making it easier to understand for beginners.
3️⃣ No dependency on DRF routers – You manually define URLs, which provides flexibility in URL structure.
Cons:
1️⃣ More boilerplate code – You need to manually handle request methods (GET
, POST
, etc.), increasing redundancy.
2️⃣ Less reusable – Unlike ViewSets, you cannot easily reuse logic across multiple views.
3️⃣ Lack of built-in DRF features – Features like pagination, filtering, and permissions require extra implementation.
Registering routes for Class based view
Talking about about routes got much more interesting with classes based views
from rest_framework_nested.routers import DefaultRouter, NestedDefaultRouter
from .views import ProductViewSet, ReviewsViewSet
router = DefaultRouter()
router.register(r'products', ProductViewSet) # Parent route
# Nested route for product reviews
reviews_router = NestedDefaultRouter(router, r'products', lookup='product')
reviews_router.register(r'reviews', ReviewsViewSet, basename='product-reviews')
urlpatterns = router.urls + reviews_router.urls
This code creates a parent-child API structure where products/
is the main route, and products/{product_id}/reviews/
is a nested route for related reviews. It uses NestedDefaultRouter
to automatically generate hierarchical URLs, reducing manual URL handling while keeping the API organized. As each review is a subset of each product.
Testing
As you can see above, which displays a list of reviews for product{1}, with the help of the ModelViewSet, you can get, create and update
With the help of DRF, you can post and create a new review for this product.
Now for accessing each review, lets access the first review by accessing the route /products/1/reviews/1
With the ModelViewSet, you can update and delete this review.
Thanks for reading.