What You Should Know About DRF, Part 3: Adding custom endpoints
This is Part 3 of a 3-part series on Django REST Framework viewsets. Read Part 1: ModelViewSet attributes and methods and Part 2: Customizing built-in methods.
I gave this talk at PyCascades 2021 and decided to turn it into a series of blog posts so it's available to folks who didn't attend the conference or don't like to watch videos. Here are the slides and the video if you want to see them.
Sometimes the endpoints you get when you use a ModelViewSet
aren't enough and you need to add extra endpoints for custom functions. To do this, you could use the APIView
class and add a custom route to your urls.py
file, and that would work fine.
But if you have a viewset already, and you feel like this new endpoint belongs with the other endpoints in your viewset, you can use DRF's @action
decorator to add a custom endpoint. This means you don't have to change your urls.py
-- the method you decorate with your @action
decorator will automatically be rendered along with the other enpdoints.
Let's continue with the library example from Part 1. Now we need a new endpoint just for featured books, books with featured = True
on the Book
model. To do this, we'll add a featured()
method to our BookViewSet
and decorate with DRF's @action
decorator.
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializers import BookDetailSerializer, BookListSerializer
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookDetailSerializer
def get_serializer_class(self):
if self.action in ["list", "featured"]:
return BookListSerializer
return super().get_serializer_class()
@action(detail=False, methods=["get"])
def featured(self, request):
books = self.get_queryset().filter(featured=True)
serializer = self.get_serializer(books, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
This code will result in this endpoint:
GET /books/featured/
Let's start with the @action
decorator.
The @action
decorator takes a couple of required arguments:
detail
:True
orFalse
, depending on whether this endpoint is expected to deal with a single object or a group of objects. Since we want to return a group of featured books, we have setdetail=False
.methods
: A list of the HTTP methods that are valid to call this endpoint. We set ours to["get"]
, so if someone tries to call our endpoint with aPOST
request, they will receive an error. This argument is actually optional and will default to["get"]
, but actions are frequently used forPOST
requests so I wanted to make sure to mention it.
Once we are in our featured()
method, we create the queryset by calling get_queryset()
and then filtering for featured=True
books. Then we get the right serializer from get_serializer()
, which will call get_serializer_class()
.
Notice that I added "featured"
to the list of actions that will return BookListSerializer
in get_serializer_class()
. The name of the action will share the name of the method.
I pass the books
queryset into the serializer, then return the data in the Response
object along with the correct HTTP status code. (The status will default to HTTP_200_OK
if you don't set it, but I set it explicitly to show you that you can.)
In Part 1: ModelViewSet attributes and methods, I covered the attributes and methods that ship with ModelViewSet
, what they do, and why you need to know about them.
In Part 2: Customizing built-in methods, I went through some real-world examples for when you might want to override some of ModelViewSet
's built-in methods.