DRF Views and Routing
Views in Django REST Framework (DRF) are the core pieces that handle incoming HTTP requests and send back the right response. They take care of processing data, running your business logic, and shaping the final output. DRF gives you several types of views, including function-based views, class-based views, and viewsets, and each one has its own strengths and best use cases.
Class-based Views
Section titled “Class-based Views”Class-based views (CBVs) in Django REST Framework give you a clean way to handle HTTP requests and responses. They let you write your API endpoints as classes, which usually keeps your code more organized and reusable compared to function-based views. CBVs come with built-in methods for handling different HTTP methods (GET, POST, PUT, DELETE, and so on), and you can easily extend them to add your own behavior.
# views.pyfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import status
class ReviewList(APIView): def get(self, request): # Logic to retrieve and return a list of reviews data = {"reviews": []} # Example data return Response(data, status=status.HTTP_200_OK)
def post(self, request): # Logic to create a new review data = {"message": "Review created successfully"} return Response(data, status=status.HTTP_201_CREATED)# urls.pyfrom django.urls import pathfrom .views import ReviewList
urlpatterns = [ path('reviews/', ReviewList.as_view(), name='review-list'),]get method
Section titled “get method”The get() method handles GET requests. It usually fetches data from the database and sends it back in the response. In the example above, we return an empty list of reviews just as a placeholder.
def get(self, request): # Logic to retrieve and return a list of reviews data = {"reviews": []} # Example data return Response(data, status=status.HTTP_200_OK)post method
Section titled “post method”The post() method handles POST requests. It usually creates a new resource using the data sent in the request. In the example, we return a success message saying that a review has been created.
def post(self, request): # Logic to create a new review data = {"message": "Review created successfully"} return Response(data, status=status.HTTP_201_CREATED)put method
Section titled “put method”The put() method handles PUT requests, which are normally used for updating an existing resource. You would write this method to update a review using the data sent in the request.
def put(self, request, *args, **kwargs): # Logic to update an existing review data = {"message": "Review updated successfully"} return Response(data, status=status.HTTP_200_OK)patch method
Section titled “patch method”The patch() method handles PATCH requests, which are used for partially updating an existing resource. This method lets you update only certain fields of a review, without touching the rest of it.
def patch(self, request, *args, **kwargs): # Logic to partially update an existing review data = {"message": "Review partially updated successfully"} return Response(data, status=status.HTTP_200_OK)delete method
Section titled “delete method”The delete() method handles DELETE requests, which are used to remove an existing resource. You would write this method to delete a review from the database based on its ID.
def delete(self, request, *args, **kwargs): # Logic to delete an existing review data = {"message": "Review deleted successfully"} return Response(data, status=status.HTTP_204_NO_CONTENT)Mixins
Section titled “Mixins”A mixin is a class that gives its methods to other classes to use, without actually being their parent class in the usual sense. In Django REST Framework, mixins are used to add common, reusable behavior to class-based views. They let you reuse the same code across many views, instead of writing it again and again.
The most commonly used mixins in DRF are:
CreateModelMixin: Gives you thecreate()method for handling POST requests that create new resources.ListModelMixin: Gives you thelist()method for handling GET requests that retrieve a list of resources.RetrieveModelMixin: Gives you theretrieve()method for handling GET requests that retrieve one single resource.UpdateModelMixin: Gives you theupdate()method for handling PUT and PATCH requests that update existing resources.DestroyModelMixin: Gives you thedestroy()method for handling DELETE requests that delete existing resources.
CreateModelMixin
Section titled “CreateModelMixin”By using CreateModelMixin, you can easily add the ability to create new resources in your API. This mixin gives you a create() method that you can call inside your view to handle POST requests.
from rest_framework import mixins, generics
class ReviewCreate(mixins.CreateModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)In this example, we define a ReviewCreate view that inherits from CreateModelMixin and GenericAPIView. We set the queryset and serializer class for the view. The post() method simply calls the create() method that the mixin already gives us, to handle creating new reviews.
ListModelMixin
Section titled “ListModelMixin”ListModelMixin lets you add the ability to fetch a list of resources in your API. This mixin gives you a list() method that you can call inside your view to handle GET requests.
from rest_framework import mixins, generics
class ReviewList(mixins.ListModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs)In this example, we define a ReviewList view that inherits from ListModelMixin and GenericAPIView. We set the queryset and serializer class for the view. The get() method calls the list() method that the mixin gives us, to handle fetching the reviews.
RetrieveModelMixin
Section titled “RetrieveModelMixin”RetrieveModelMixin lets you add the ability to fetch one single resource in your API. This mixin gives you a retrieve() method that you can call inside your view to handle GET requests for one specific resource.
from rest_framework import mixins, generics
class ReviewDetail(mixins.RetrieveModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs)In this example, we define a ReviewDetail view that inherits from RetrieveModelMixin and GenericAPIView. We set the queryset and serializer class for the view. The get() method calls the retrieve() method that the mixin gives us, to fetch a specific review based on its ID.
UpdateModelMixin
Section titled “UpdateModelMixin”UpdateModelMixin lets you add the ability to update existing resources in your API. This mixin gives you an update() method that you can call inside your view to handle PUT and PATCH requests.
from rest_framework import mixins, genericsclass ReviewUpdate(mixins.UpdateModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs)In this example, we define a ReviewUpdate view that inherits from UpdateModelMixin and GenericAPIView. We set the queryset and serializer class for the view. The put() method calls the update() method that the mixin gives us, to update an existing review based on its ID.
DestroyModelMixin
Section titled “DestroyModelMixin”DestroyModelMixin lets you add the ability to delete existing resources in your API. This mixin gives you a destroy() method that you can call inside your view to handle DELETE requests.
from rest_framework import mixins, genericsclass ReviewDelete(mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)In this example, we define a ReviewDelete view that inherits from DestroyModelMixin and GenericAPIView. We set the queryset and serializer class for the view. The delete() method calls the destroy() method that the mixin gives us, to remove an existing review based on its ID.
Generic Views
Section titled “Generic Views”Django REST Framework also gives you a set of generic views, which already combine the mixins for you to handle common API patterns. These generic views are built for common situations like listing resources, creating new resources, retrieving one resource, updating resources, and deleting resources.
The most commonly used generic views in DRF are:
ListCreateAPIView: CombinesListModelMixinandCreateModelMixinto handle both listing and creating resources.RetrieveUpdateDestroyAPIView: CombinesRetrieveModelMixin,UpdateModelMixin, andDestroyModelMixinto handle retrieving, updating, and deleting resources.ListAPIView: Gives you the ability to list resources.CreateAPIView: Gives you the ability to create new resources.RetrieveAPIView: Gives you the ability to retrieve one resource.UpdateAPIView: Gives you the ability to update existing resources.DestroyAPIView: Gives you the ability to delete existing resources.GenericAPIView: A base class that gives you the core functionality used by all generic views, which you can extend to build your own custom views.
Each class-based view and mixin in DRF has its own clear purpose, which lets you build your API endpoints efficiently. By using these tools, you can build solid, easy-to-maintain APIs while writing less repeated code.
ListCreateAPIView
Section titled “ListCreateAPIView”ListCreateAPIView is a generic view that combines ListModelMixin and CreateModelMixin. It lets you handle both listing resources and creating new resources inside one single view.
from rest_framework import generics
class ReviewListCreate(generics.ListCreateAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewListCreate view that inherits from ListCreateAPIView. We set the queryset and serializer class for the view. This view will handle GET requests to list all reviews, and POST requests to create new reviews.
RetrieveUpdateDestroyAPIView
Section titled “RetrieveUpdateDestroyAPIView”RetrieveUpdateDestroyAPIView is a generic view that combines RetrieveModelMixin, UpdateModelMixin, and DestroyModelMixin. It lets you handle retrieving, updating, and deleting resources inside one single view.
from rest_framework import generics
class ReviewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewRetrieveUpdateDestroy view that inherits from RetrieveUpdateDestroyAPIView. We set the queryset and serializer class for the view. This view will handle GET requests to fetch a single review, PUT and PATCH requests to update an existing review, and DELETE requests to delete an existing review.
ListAPIView
Section titled “ListAPIView”ListAPIView is a generic view that gives you the ability to list resources. It is a read-only view that handles GET requests to fetch a list of resources.
from rest_framework import generics
class ReviewList(generics.ListAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewList view that inherits from ListAPIView. We set the queryset and serializer class for the view. This view will handle GET requests to fetch a list of all reviews.
CreateAPIView
Section titled “CreateAPIView”CreateAPIView is a generic view that gives you the ability to create new resources. It is a write-only view that handles POST requests to create new resources.
from rest_framework import generics
class ReviewCreate(generics.CreateAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewCreate view that inherits from CreateAPIView. We set the queryset and serializer class for the view. This view will handle POST requests to create new reviews.
RetrieveAPIView
Section titled “RetrieveAPIView”RetrieveAPIView is a generic view that gives you the ability to fetch one single resource. It is a read-only view that handles GET requests to fetch a specific resource based on its ID.
from rest_framework import generics
class ReviewDetail(generics.RetrieveAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewDetail view that inherits from RetrieveAPIView. We set the queryset and serializer class for the view. This view will handle GET requests to fetch a specific review based on its ID.
UpdateAPIView
Section titled “UpdateAPIView”UpdateAPIView is a generic view that gives you the ability to update existing resources. It is a write-only view that handles PUT and PATCH requests to update existing resources.
from rest_framework import generics
class ReviewUpdate(generics.UpdateAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewUpdate view that inherits from UpdateAPIView. We set the queryset and serializer class for the view. This view will handle PUT and PATCH requests to update an existing review based on its ID.
DestroyAPIView
Section titled “DestroyAPIView”DestroyAPIView is a generic view that gives you the ability to delete existing resources. It is a write-only view that handles DELETE requests to delete existing resources.
from rest_framework import generics
class ReviewDelete(generics.DestroyAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializerIn this example, we define a ReviewDelete view that inherits from DestroyAPIView. We set the queryset and serializer class for the view. This view will handle DELETE requests to delete an existing review based on its ID.
GenericAPIView / Custom Generic Views
Section titled “GenericAPIView / Custom Generic Views”GenericAPIView is a base class that gives you the core functionality shared by all generic views in Django REST Framework. It is not meant to be used directly, but you can extend it to build custom views for behavior that the built-in generic views do not already cover.
This class does not have any default behavior for handling HTTP methods, so you need to write the methods (get, post, put, patch, delete) yourself to decide how the view should respond to each type of request.
from rest_framework import generics
class ReviewView(generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def get(self, request, *args, **kwargs): # Custom logic for handling GET requests data = {"message": "Custom GET response"} return Response(data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs): # Custom logic for handling POST requests data = {"message": "Custom POST response"} return Response(data, status=status.HTTP_201_CREATED)In this example, we define a ReviewView that inherits from GenericAPIView. We set the queryset and serializer class for the view. We also write our own custom logic for handling GET and POST requests, so we can shape the behavior exactly the way we want.
You can also use this together with mixins to build custom views that combine the functionality of several mixins, while still letting you write your own behavior for specific HTTP methods.
from rest_framework import generics, mixins
class ReviewListCreateView( mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)In this example, we defined a ReviewListCreateView that inherits from GenericAPIView and uses ListModelMixin and CreateModelMixin together, to give us the ability to list and create reviews.
This same approach is used inside the built-in generic views like ListCreateAPIView and RetrieveUpdateDestroyAPIView, which combine several mixins to give you common API patterns, while still letting you customize things by overriding methods.
Methods and Attributes
Section titled “Methods and Attributes”As you can see in the examples above, queryset and serializer_class are class attributes set at the class level. The generic views use these attributes to know which queryset to use for fetching data, and which serializer to use for converting data back and forth. But for more complex situations, you can also override methods like get_queryset(), get_serializer_class(), and get_serializer_context() to add dynamic behavior based on the request or other conditions.
Example of overriding get_queryset(), get_serializer_class(), and get_serializer_context() methods:
from rest_framework import generics
class ReviewList(generics.ListAPIView): serializer_class = ReviewSerializer
def get_queryset(self): # Custom logic to determine the queryset based on request parameters product_id = self.request.query_params.get('product_id') if product_id: return Review.objects.filter(product_id=product_id) return Review.objects.all()
def get_serializer_class(self): # Custom logic to determine the serializer class based on request parameters if self.request.query_params.get('detailed'): return DetailedReviewSerializer return ReviewSerializer
def get_serializer_context(self): # Custom logic to provide additional context to the serializer context = super().get_serializer_context() context['request'] = self.request return context # or return {'request': self.request} if you don't need the default contextIn this example, we override get_queryset() to filter reviews based on a product_id query parameter. We also override get_serializer_class() to return a different serializer if a detailed query parameter is present. Finally, we override get_serializer_context() to include the request inside the serializer context, which can be useful for building full URLs or reading request data from inside the serializer.
Overriding Methods
Section titled “Overriding Methods”If you want to add your own behavior to any of the HTTP methods (GET, POST, PUT, PATCH, DELETE), you can override the matching method in your view. For example, if you want to add custom logic before handling a DELETE request in a RetrieveUpdateDestroyAPIView, you can override the delete method like this:
from rest_framework import generics
class ReviewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer
def delete(self, request, *args, **kwargs): # Custom logic before deleting the review review = self.get_object() if review.is_protected: return Response({"error": "This review cannot be deleted."}, status=status.HTTP_403_FORBIDDEN)
# Call the default destroy method to delete the review return super().delete(request, *args, **kwargs)lookup_field
Section titled “lookup_field”By default, generic views use the pk field to find a specific resource. But you can change this by setting the lookup_field attribute on your view. For example, if you want to use a slug field instead of pk, you can do it like this:
class ReviewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView): queryset = Review.objects.all() serializer_class = ReviewSerializer lookup_field = 'slug'In this example, the view will look for a slug field in the URL to retrieve, update, or delete a review, instead of using the default pk field.
ViewSets
Section titled “ViewSets”ViewSets in Django REST Framework give you a clean way to combine the logic for a group of related views into a single class. They let you define the behavior for several HTTP methods (GET, POST, PUT, PATCH, DELETE) all inside one class, which helps cut down on repeated code and makes your code easier to maintain.
The most commonly used ViewSets in DRF are:
ModelViewSet: Gives you the full set of default read and write operations (list, create, retrieve, update, partial_update, destroy) for a model.ReadOnlyModelViewSet: Gives you default read-only operations (list and retrieve) for a model.GenericViewSet: A base class that gives you the core functionality shared by all viewsets, which you can extend to build your own custom viewsets.
ModelViewSet
Section titled “ModelViewSet”ModelViewSet is a viewset that gives you the full set of default read and write operations for a model. It combines all the mixins and generic views together to give you a complete set of CRUD operations.
from rest_framework import viewsets
class ReviewViewSet(viewsets.ModelViewSet): queryset = Review.objects.all() serializer_class = ReviewSerializer
# you can also override the default methods to add custom behavior # if needed i.e. the list(), create(), retrieve(), update(), # partial_update(), and destroy() methods to handle GET, POST, # PUT, PATCH, and DELETE requests respectively.
# you can also add get_queryset(), get_serializer_class(), # and get_serializer_context() methods to provide dynamic # behavior based on the request or other factorsIn this example, we define a ReviewViewSet that inherits from ModelViewSet. We set the queryset and serializer class for the viewset. This viewset will handle all the CRUD operations for the Review model, including listing reviews, creating new reviews, retrieving a single review, updating existing reviews, and deleting reviews.
ReadOnlyModelViewSet
Section titled “ReadOnlyModelViewSet”ReadOnlyModelViewSet is a viewset that gives you default read-only operations for a model. It combines ListModelMixin and RetrieveModelMixin together, so you can list resources and retrieve a single resource.
from rest_framework import viewsets
class ReviewReadOnlyViewSet(viewsets.ReadOnlyModelViewSet): queryset = Review.objects.all() serializer_class = ReviewSerializer
# you can also override the default methods to add # custom behavior if needed but this viewset will only # handle GET requests for listing and retrieving reviews, # and it will not allow creating, updating, or deleting # reviews. i.e. the list() and retrieve() methods to # handle GET requests for listing and retrieving reviews # respectively.
# you can also add get_queryset(), get_serializer_class(), # and get_serializer_context() methods to provide dynamic # behavior based on the request or other factors
def destroy(self, request, *args, **kwargs): # check the like count if it is greater than 0 # we consider it as protected review and prevent deletion
if Review.objects.filter(id=review.id, like_count__gt=0).exists(): return Response( {"error": "This review cannot be deleted."}, status=status.HTTP_403_FORBIDDEN )
# or
review = self.get_object() if review.like_count > 0: return Response( {"error": "This review cannot be deleted."}, status=status.HTTP_403_FORBIDDEN )
return super().destroy(request, *args, **kwargs)In this example, we define a ReviewReadOnlyViewSet that inherits from ReadOnlyModelViewSet. We set the queryset and serializer class for the viewset. This viewset will handle GET requests to list all reviews and fetch a specific review based on its ID, but it will not allow creating, updating, or deleting reviews.
GenericViewSet
Section titled “GenericViewSet”GenericViewSet is a base class that gives you the core functionality shared by all viewsets. It does not give you any default behavior for handling HTTP methods, so you need to write the methods (list, create, retrieve, update, partial_update, destroy) yourself to decide how the viewset should respond to each type of request.
from rest_framework import viewsets
class ReviewViewSet(viewsets.GenericViewSet): queryset = Review.objects.all() serializer_class = ReviewSerializer
def list(self, request, *args, **kwargs): # Custom logic for handling GET requests to list reviews data = {"message": "Custom list response"} return Response(data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs): # Custom logic for handling POST requests to create a review data = {"message": "Custom create response"} return Response(data, status=status.HTTP_201_CREATED)GenericViewSet lets you write your own behavior for each HTTP method, by writing the matching method inside your viewset. In this example, we write a list() method to handle GET requests for listing reviews, and a create() method to handle POST requests for creating a new review. You can also write retrieve(), update(), partial_update(), and destroy() methods to handle GET requests for fetching one review, PUT and PATCH requests for updating reviews, and DELETE requests for deleting reviews.
You can also use this together with mixins to build custom viewsets that combine several mixins, while still letting you write your own behavior for specific HTTP methods.
from rest_framework import viewsets, mixins
class ReviewViewSet( mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet,): queryset = Review.objects.all() serializer_class = ReviewSerializer
def list(self, request, *args, **kwargs): # Custom logic for handling GET requests to list reviews data = {"message": "Custom list response"} return Response(data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs): # Custom logic for handling POST requests to create a review data = {"message": "Custom create response"} return Response(data, status=status.HTTP_201_CREATED)
def retrieve(self, request, *args, **kwargs): # Custom logic for handling GET requests to retrieve a single review data = {"message": "Custom retrieve response"} return Response(data, status=status.HTTP_200_OK)Routers
Section titled “Routers”Routers in Django REST Framework give you a simple way to automatically build the URL patterns for your viewsets. They handle mapping HTTP methods to the right actions in your viewset, so you do not have to manually write the URL patterns for each action.
The most commonly used routers in DRF are:
DefaultRouter: Automatically creates URL patterns for all the standard actions (list, create, retrieve, update, partial_update, destroy) in a viewset. It also adds a default API root view that lists all the registered viewsets.SimpleRouter: Works likeDefaultRouter, but it does not include the default API root view. It only creates URL patterns for the standard actions in a viewset.NestedRouter: A third-party router from thedrf-nested-routerspackage that lets you build nested routes for related resources.
DefaultRouter
Section titled “DefaultRouter”DefaultRouter is a router that automatically creates URL patterns for all the standard actions in a viewset. It also adds a default API root view that lists all the registered viewsets.
from rest_framework import routersfrom .views import ReviewViewSet
router = routers.DefaultRouter()router.register(r'reviews', ReviewViewSet, basename='review')
urlpatterns = router.urls# orurlpatterns = [ path('', include(router.urls)),]In this example, we create a DefaultRouter instance and register our ReviewViewSet with it. The register() method takes three arguments: the URL prefix (in this case, ‘reviews’), the viewset class, and an optional basename for the viewset. The router will automatically create URL patterns for all the standard actions in ReviewViewSet, such as:
GET /reviews/for listing reviewsPOST /reviews/for creating a new reviewGET /reviews/{pk}/for retrieving a specific reviewPUT /reviews/{pk}/for updating an existing reviewPATCH /reviews/{pk}/for partially updating an existing reviewDELETE /reviews/{pk}/for deleting an existing review
Additional Features of DefaultRouter
Section titled “Additional Features of DefaultRouter”- It also adds a default API root view that lists all the registered viewsets, which you can open at the root URL (for example,
/). - If you add
.jsonto the end of the URL, it will return a JSON response instead of an HTML page.
SimpleRouter
Section titled “SimpleRouter”SimpleRouter works much like DefaultRouter, but it does not include the default API root view. It only creates URL patterns for the standard actions in a viewset.
from rest_framework import routersfrom .views import ReviewViewSet
router = routers.SimpleRouter()router.register(r'reviews', ReviewViewSet, basename='review')
urlpatterns = router.urls# orurlpatterns = [ path('', include(router.urls)),]In this example, we create a SimpleRouter instance and register our ReviewViewSet with it. The register() method works the same way as in DefaultRouter, but SimpleRouter will not include the default API root view that lists all the registered viewsets. It will only create URL patterns for the standard actions in ReviewViewSet.
Comparison between DefaultRouter and SimpleRouter
Section titled “Comparison between DefaultRouter and SimpleRouter”| Feature | DefaultRouter | SimpleRouter |
|---|---|---|
| API Root View | Yes | No |
| Root URL (/) | Lists all endpoints | No root endpoint |
| URL Generation | Same as SimpleRouter | Same as DefaultRouter |
| Format Suffix | Supported (.json, etc.) | Limited / manual setup |
| Use Case | Browsable API / Dev | Clean APIs / Production |
| Complexity | Slightly more | Minimal |
| Control | Less control over root | More control |
Nested Routers
Section titled “Nested Routers”Nested routers is a third-party package that gives you a way to build nested routes for related resources in Django REST Framework. It lets you build URL patterns that reflect the relationships between your models, which makes it easier to work with related data in your API.
To install the drf-nested-routers package
uv add drf-nested-routersWe will create a nested route for products and their reviews.
/products/for listing and creating products/products/{pk}for retrieving, updating, and deleting a specific product/products/{product_pk}/reviews/for listing and creating reviews for a specific product/products/{product_pk}/reviews/{pk}/for retrieving, updating, and deleting a specific review for a specific product
Have you noticed that we use
pkfor the product’s identifier, but in the reviews URL we useproduct_pkfor the product’s identifier andpkfor the review’s identifier? This happens because when you use nested routers, the parent resource’s identifier gets added to the URL as a separate parameter (likeproduct_pk), while the child resource’s identifier is still read usingpkinside the viewset.
# models.pyfrom django.db import models
class Product(models.Model): name = models.CharField(max_length=100) description = models.TextField()
class Review(models.Model): product = models.ForeignKey(Product, related_name='reviews', on_delete=models.CASCADE) content = models.TextField()# serializers.pyfrom rest_framework import serializersfrom .models import Product, Review
class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ['id', 'name', 'description']
class ReviewSerializer(serializers.ModelSerializer): class Meta: model = Review fields = [ 'id', #'product', # we will set the product field in the # view when creating a review, so we # don't need to include it in the # serializer fields 'content' ]
def create(self, validated_data): # Ensure that the product is associated with the review when creating a new review product_id = self.context['product_pk'] return Review.objects.create(product_id=product_id, **validated_data)# views.pyfrom rest_framework import viewsetsfrom .models import Product, Review
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer
class ReviewViewSet(viewsets.ModelViewSet): serializer_class = ReviewSerializer
def get_queryset(self): # Filter reviews based on the product_pk from the URL product_id = self.kwargs.get('product_pk') return Review.objects.filter(product_id=product_id)
def get_serializer_context(self): # Include the product_pk in the serializer context to use it in the create method context = super().get_serializer_context() context['product_pk'] = self.kwargs.get('product_pk') return contextNow we can use NestedDefaultRouter from the drf-nested-routers package to build nested routes for products and their reviews.
# urls.pyfrom django.urls import path, includefrom rest_framework_nested import routersfrom .views import ProductViewSet, ReviewViewSet
router = routers.DefaultRouter()
# this will create the following URL patterns:# - /products/ for listing and creating products# - /products/{pk}/ for retrieving, updating, and deleting a specific productrouter.register(r'products', ProductViewSet, basename='product')
# this will create the following nested URL patterns for reviews:# - /products/{product_pk}/reviews/ for listing and creating reviews for a specific product# - /products/{product_pk}/reviews/{pk}/ for retrieving, updating, and deleting a specific review for a specific productproducts_router = routers.NestedDefaultRouter(router, r'products', lookup='product')products_router.register(r'reviews', ReviewViewSet, basename='product-reviews')
urlpatterns = [ path('', include(router.urls)), path('', include(products_router.urls)),]Let’s break down what we did here:
products_router = routers.NestedDefaultRouter(router, r'products', lookup='product')
- We create a
NestedDefaultRouterinstance calledproducts_router. - The first argument is the parent router (
router) that we want to nest this under. - The second argument is the prefix for the parent resource (
r'products'), which should match the prefix we used when registeringProductViewSetwith the parent router. - The
lookupargument sets the name of the URL parameter used to identify the parent resource in the nested routes. Here, we uselookup='product', which means the parent resource’s identifier will show up asproduct_pkin the URL, and you can read it inside the viewset usingself.kwargs.get('product_pk'). In other words, it adds aproduct_prefix to the parent resource’s identifier in the URL.