Skip to content

DRF Views and Routing

Class-based views (CBVs) in Django REST Framework provide a powerful way to handle HTTP requests and responses. They allow you to define your API endpoints as classes, which can be more organized and reusable compared to function-based views. CBVs come with built-in methods for handling different HTTP methods (GET, POST, PUT, DELETE, etc.) and can be easily extended to add custom behavior.

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from 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.py
from django.urls import path
from .views import ReviewList
urlpatterns = [
path('reviews/', ReviewList.as_view(), name='review-list'),
]

The get() method is used to handle GET requests. It typically retrieves data from the database and returns it in a response. In the example above, we return an empty list of reviews 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)

The post() method is used to handle POST requests. It usually involves creating a new resource based on the data sent in the request. In the example, we return a success message indicating 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)

The put() method is used to handle PUT requests, which are typically used for updating existing resources. You would implement this method to update a review based on the data provided 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)

The patch() method is used to handle PATCH requests, which are used for partially updating existing resources. This method allows you to update only specific fields of a review without affecting the entire resource.

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)

The delete() method is used to handle DELETE requests, which are used to delete existing resources. You would implement this method to remove a review from the database based on its identifier.

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)

A mixin is a class that provides methods to be used by other classes without being a parent class. In Django REST Framework, mixins are used to add common behavior to class-based views. They allow you to reuse code across multiple views without having to duplicate it.

The most commonly used mixins in DRF are:

  • CreateModelMixin: Provides the create() method for handling POST requests to create new resources.
  • ListModelMixin: Provides the list() method for handling GET requests to retrieve a list of resources.
  • RetrieveModelMixin: Provides the retrieve() method for handling GET requests to retrieve a single resource.
  • UpdateModelMixin: Provides the update() method for handling PUT and PATCH requests to update existing resources.
  • DestroyModelMixin: Provides the destroy() method for handling DELETE requests to delete existing resources.

By using the CreateModelMixin, you can easily implement the functionality to create new resources in your API. This mixin provides a create() method that you can call in 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 specify the queryset and serializer class for the view. The post() method calls the create() method provided by the mixin to handle the creation of new reviews.

The ListModelMixin allows you to implement the functionality to retrieve a list of resources in your API. This mixin provides a list() method that you can call in 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 specify the queryset and serializer class for the view. The get() method calls the list() method provided by the mixin to handle the retrieval of reviews.

The RetrieveModelMixin allows you to implement the functionality to retrieve a single resource in your API. This mixin provides a retrieve() method that you can call in your view to handle GET requests for a 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 specify the queryset and serializer class for the view. The get() method calls the retrieve() method provided by the mixin to handle the retrieval of a specific review based on its identifier.

The UpdateModelMixin allows you to implement the functionality to update existing resources in your API. This mixin provides an update() method that you can call in your view to handle PUT and PATCH requests.

from rest_framework import mixins, generics
class 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 specify the queryset and serializer class for the view. The put() method calls the update() method provided by the mixin to handle the updating of an existing review based on its identifier.

The DestroyModelMixin allows you to implement the functionality to delete existing resources in your API. This mixin provides a destroy() method that you can call in your view to handle DELETE requests.

from rest_framework import mixins, generics
class 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 specify the queryset and serializer class for the view. The delete() method calls the destroy() method provided by the mixin to handle the deletion of an existing review based on its identifier.

Django REST Framework provides a set of generic views that combine the functionality of mixins to create common API patterns. These generic views are designed to handle common use cases such as listing resources, creating new resources, retrieving a single resource, updating existing resources, and deleting resources.

The most commonly used generic views in DRF are:

  • ListCreateAPIView: Combines the functionality of ListModelMixin and CreateModelMixin to handle both listing and creating resources.
  • RetrieveUpdateDestroyAPIView: Combines the functionality of RetrieveModelMixin, UpdateModelMixin, and DestroyModelMixin to handle retrieving, updating, and deleting resources.
  • ListAPIView: Provides the functionality to list resources.
  • CreateAPIView: Provides the functionality to create new resources.
  • RetrieveAPIView: Provides the functionality to retrieve a single resource.
  • UpdateAPIView: Provides the functionality to update existing resources.
  • DestroyAPIView: Provides the functionality to delete existing resources.
  • GenericAPIView: A base class that provides the core functionality for all generic views, which can be extended to create custom views.

Each class-based view and mixin in DRF serves a specific purpose, allowing you to build your API endpoints efficiently. By using these tools, you can create robust and maintainable APIs with minimal code duplication.

The ListCreateAPIView is a generic view that combines the functionality of ListModelMixin and CreateModelMixin. It allows you to handle both listing resources and creating new resources in a single view.

from rest_framework import generics
class ReviewListCreate(generics.ListCreateAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer

In this example, we define a ReviewListCreate view that inherits from ListCreateAPIView. We specify 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.

The RetrieveUpdateDestroyAPIView is a generic view that combines the functionality of RetrieveModelMixin, UpdateModelMixin, and DestroyModelMixin. It allows you to handle retrieving, updating, and deleting resources in a single view.

from rest_framework import generics
class ReviewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer

In this example, we define a ReviewRetrieveUpdateDestroy view that inherits from RetrieveUpdateDestroyAPIView. We specify the queryset and serializer class for the view. This view will handle GET requests to retrieve a single review, PUT and PATCH requests to update an existing review, and DELETE requests to delete an existing review.

The ListAPIView is a generic view that provides the functionality to list resources. It is a read-only view that handles GET requests to retrieve a list of resources.

from rest_framework import generics
class ReviewList(generics.ListAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer

In this example, we define a ReviewList view that inherits from ListAPIView. We specify the queryset and serializer class for the view. This view will handle GET requests to retrieve a list of all reviews.

The CreateAPIView is a generic view that provides the functionality 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 = ReviewSerializer

In this example, we define a ReviewCreate view that inherits from CreateAPIView. We specify the queryset and serializer class for the view. This view will handle POST requests to create new reviews.

The RetrieveAPIView is a generic view that provides the functionality to retrieve a single resource. It is a read-only view that handles GET requests to retrieve a specific resource based on its identifier.

from rest_framework import generics
class ReviewDetail(generics.RetrieveAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer

In this example, we define a ReviewDetail view that inherits from RetrieveAPIView. We specify the queryset and serializer class for the view. This view will handle GET requests to retrieve a specific review based on its identifier.

The UpdateAPIView is a generic view that provides the functionality 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 = ReviewSerializer

In this example, we define a ReviewUpdate view that inherits from UpdateAPIView. We specify the queryset and serializer class for the view. This view will handle PUT and PATCH requests to update an existing review based on its identifier.

The DestroyAPIView is a generic view that provides the functionality 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 = ReviewSerializer

In this example, we define a ReviewDelete view that inherits from DestroyAPIView. We specify the queryset and serializer class for the view. This view will handle DELETE requests to delete an existing review based on its identifier.

The GenericAPIView is a base class that provides the core functionality for all generic views in Django REST Framework. It is not meant to be used directly but can be extended to create custom views that require specific behavior not provided by the built-in generic views.

This class dont have any default behavior for handling HTTP methods, so you need to implement the methods (get, post, put, patch, delete) yourself to define how the view should respond to different types of requests.

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 specify the queryset and serializer class for the view. We also implement custom logic for handling GET and POST requests, allowing us to tailor the behavior of the view to our specific needs.

You can use this with mixins to create custom views that combine the functionality of multiple mixins while still allowing you to implement custom 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 the ListModelMixin and CreateModelMixin to provide the functionality for listing and creating reviews.

This the similar approach is used in the built-in generic views like ListCreateAPIView and RetrieveUpdateDestroyAPIView, which combine multiple mixins to provide common API patterns while still allowing for customization through method overrides.

As you can see in the above examples, queryset and serializer_class are class attributes that are defined at the class level. These attributes are used by the generic views to determine which queryset to use for retrieving data and which serializer to use for serializing and deserializing data. But for complex scenarios, you can also override methods like get_queryset(), get_serializer_class() and get_serializer_context() to provide dynamic behavior based on the request or other factors.

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 context

In this example, we override the get_queryset() method to filter reviews based on a product_id query parameter. We also override the get_serializer_class() method to return a different serializer if a detailed query parameter is present. Finally, we override the get_serializer_context() method to include the request in the serializer context, which can be useful for generating absolute URLs or accessing request data within the serializer.

If you want to implement custom behavior for any of the HTTP methods (GET, POST, PUT, PATCH, DELETE), you can override the corresponding method in your view. For example, if you want to add custom logic to the DELETE request handling in a RetrieveUpdateDestroyAPIView, you can override the delete method as follows:

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)

By default, generic views use the pk field to look up individual resources. However, you can change this behavior by setting the lookup_field attribute in your view. For example, if you want to use a slug field instead of pk, you can do so as follows:

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 in Django REST Framework are a powerful way to combine the logic for a set of related views into a single class. They allow you to define the behavior for multiple HTTP methods (GET, POST, PUT, PATCH, DELETE) in a single class, which can help reduce code duplication and improve maintainability.

The most commonly used ViewSets in DRF are:

  • ModelViewSet: Provides the full set of default read and write operations (list, create, retrieve, update, partial_update, destroy) for a model.
  • ReadOnlyModelViewSet: Provides default read-only operations (list and retrieve) for a model.
  • GenericViewSet: A base class that provides the core functionality for all viewsets, which can be extended to create custom viewsets.

The ModelViewSet is a viewset that provides the full set of default read and write operations for a model. It combines the functionality of all the mixins and generic views to provide 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 factors

In this example, we define a ReviewViewSet that inherits from ModelViewSet. We specify 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.

The ReadOnlyModelViewSet is a viewset that provides default read-only operations for a model. It combines the functionality of the ListModelMixin and RetrieveModelMixin to provide the ability to 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 specify the queryset and serializer class for the viewset. This viewset will handle GET requests to list all reviews and retrieve a specific review based on its identifier, but it will not allow creating, updating, or deleting reviews.

The GenericViewSet is a base class that provides the core functionality for all viewsets. It does not provide any default behavior for handling HTTP methods, so you need to implement the methods (list, create, retrieve, update, partial_update, destroy) yourself to define how the viewset should respond to different types of requests.

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)

The GenericViewSet allows you to define custom behavior for each of the HTTP methods by implementing the corresponding methods in your viewset. In this example, we implement the list() method to handle GET requests for listing reviews and the create() method to handle POST requests for creating a new review. You can also implement the retrieve(), update(), partial_update(), and destroy() methods to handle GET requests for retrieving a single review, PUT and PATCH requests for updating existing reviews, and DELETE requests for deleting reviews, respectively.

You can use this with mixins to create custom viewsets that combine the functionality of multiple mixins while still allowing you to implement custom 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 in Django REST Framework are a powerful way to automatically generate URL patterns for your viewsets. They provide a convenient way to map HTTP methods to the appropriate actions in your viewset without having to manually define the URL patterns for each action.

The most commonly used routers in DRF are:

  • DefaultRouter: Automatically generates URL patterns for all the standard actions (list, create, retrieve, update, partial_update, destroy) in a viewset. It also includes a default API root view that lists all the registered viewsets.
  • SimpleRouter: Similar to DefaultRouter, but it does not include the default API root view. It only generates URL patterns for the standard actions in a viewset.
  • NestedRouter: A third-party router provided by the drf-nested-routers package that allows you to create nested routes for related resources.

The DefaultRouter is a router that automatically generates URL patterns for all the standard actions in a viewset. It also includes a default API root view that lists all the registered viewsets.

from rest_framework import routers
from .views import ReviewViewSet
router = routers.DefaultRouter()
router.register(r'reviews', ReviewViewSet, basename='review')
urlpatterns = router.urls
# or
urlpatterns = [
path('', include(router.urls)),
]

In this example, we create a DefaultRouter instance and register our ReviewViewSet with the router. The register() method takes three arguments: the prefix for the URL (in this case, ‘reviews’), the viewset class, and an optional basename for the viewset. The router will automatically generate URL patterns for all the standard actions in the ReviewViewSet, such as:

  • GET /reviews/ for listing reviews
  • POST /reviews/ for creating a new review
  • GET /reviews/{pk}/ for retrieving a specific review
  • PUT /reviews/{pk}/ for updating an existing review
  • PATCH /reviews/{pk}/ for partially updating an existing review
  • DELETE /reviews/{pk}/ for deleting an existing review
  • It also includes a default API root view that lists all the registered viewsets, which can be accessed at the root URL (e.g., /).
  • If you add .json to the end of the URL, it will return a JSON response instead of HTML.

The SimpleRouter is similar to the DefaultRouter, but it does not include the default API root view. It only generates URL patterns for the standard actions in a viewset.

from rest_framework import routers
from .views import ReviewViewSet
router = routers.SimpleRouter()
router.register(r'reviews', ReviewViewSet, basename='review')
urlpatterns = router.urls
# or
urlpatterns = [
path('', include(router.urls)),
]

In this example, we create a SimpleRouter instance and register our ReviewViewSet with the router. The register() method works the same way as in the DefaultRouter, but the SimpleRouter will not include the default API root view that lists all the registered viewsets. It will only generate URL patterns for the standard actions in the ReviewViewSet.

Comparison between DefaultRouter and SimpleRouter

Section titled “Comparison between DefaultRouter and SimpleRouter”
FeatureDefaultRouterSimpleRouter
API Root ViewYesNo
Root URL (/)Lists all endpointsNo root endpoint
URL GenerationSame as SimpleRouterSame as DefaultRouter
Format SuffixSupported (.json, etc.)Limited / manual setup
Use CaseBrowsable API / DevClean APIs / Production
ComplexitySlightly moreMinimal
ControlLess control over rootMore control

Nested routers is a third-party package that provides a way to create nested routes for related resources in Django REST Framework. It allows you to define URL patterns that reflect the relationships between your models, making it easier to work with related data in your API.

To install the drf-nested-routers package

Terminal window
uv add drf-nested-routers

We 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 are using pk for the products identifier but in the reviews URL we are using product_pk for the product identifier and pk for the review identifier? This is because when we use nested routers, the parent resource’s identifier is included in the URL as a separate parameter (e.g., product_pk), while the child resource’s identifier is still accessed using pk in the viewset.

# models.py
from 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.py
from rest_framework import serializers
from .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.py
from rest_framework import viewsets
from .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 context

Now we can use the NestedDefaultRouter from the drf-nested-routers package to create nested routes for products and their reviews.

# urls.py
from django.urls import path, include
from rest_framework_nested import routers
from .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 product
router.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 product
products_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 NestedDefaultRouter instance called products_router.
  • The first argument is the parent router (router) that we want to nest under.
  • The second argument is the prefix for the parent resource (r'products'), which should match the prefix used when registering the ProductViewSet with the parent router.
  • The lookup argument specifies the name of the URL parameter that will be used to identify the parent resource in the nested routes. In this case, we use lookup='product', which means that the parent resource’s identifier will be available as product_pk in the URL and can be accessed in the viewset using self.kwargs.get('product_pk'). i.e. it add a prefix of product_ to the parent resource’s identifier in the URL.