Author: Karen
Categories: TECH
Tags: Django Python Django Rest Framework

Django Rest Framework (DRF) is a framework for building web APIs using Django, making the process of building reusable APIs very easy and efficient. It exposes Django models and business logic as RESTful services, handling common API tasks like serialization, authentication, permissions, and request/response formatting. Some of the key functionalities of DRF include:

  • Serialization: Convert complex data types (e.g., Django models, querysets) to and from JSON, XML, and other formats
  • Request & Response handling
  • Viewsets & Generic Views: Rapid API development with reusable, class-based views
  • Authentication: Built-in support for session, token, JWT (via extensions), OAuth, and custom authentication 
  • Permissions & Authorization: Fine-grained access control at global, view, or object level
  • Pagination: Easy handling of large datasets with customizable pagination styles
  • Filtering & Searching: Built-in filtering, search, and ordering support

This post focuses on building APIs and performing serialization using DRF’s built-in classes, gradually moving from low-level views to higher-level abstractions.

The most basic class implemented by DRF is the APIView class. It gives you full control over request handling while still providing DRF features such as authentication and permissions. Let's consider the following sample Django model:

class Book(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)

Basic GET APIs with APIView

To create APIs for retrieving all books or a single book by ID, we can implement a GET handler like this:

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from events.models import Book


class BookView(APIView):

    def get(self, request, book_id=None):
        if book_id is not None:
            book = get_object_or_404(Book, id=book_id)
            return Response(self.serialize_response(book))

        books = Book.objects.all()
        return Response([self.serialize_response(b) for b in books])

    def serialize_response(self, book):
        return {
            "id": book.id,
            "name": book.name,
            "category": book.category_id,
        }

with URL configuration

path("books/<int:book_id>/", BookView.as_view(), name='book-list'),
path("books/", BookView.as_view(), name='book-details')

If book_id is provided, the API returns a single serialized book. Otherwise, it returns a list of all books.

Creating, Updating, and Deleting with APIView

To support creating, updating, and deleting books, we add POST, PUT, and DELETE methods:

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from events.models import Book


class BookView(APIView):

    def post(self, request):
        data = request.data

        book = Book.objects.create(
            name=data.get("name"),
            category_id=data.get("category"),
        )

        return Response(
            self.serialize_response(book),
            status=status.HTTP_201_CREATED
        )

    def put(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        data = request.data

        book.name = data.get("name", book.name)
        book.category_id = data.get("category", book.category_id)
        book.save()

        return Response(
            self.serialize_response(book),
            status=status.HTTP_200_OK
        )

    def delete(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def serialize_response(self, book):
        return {
            "id": book.id,
            "name": book.name,
            "category": book.category_id,
        }

DRF’s browsable API allows you to navigate to endpoints such as /books/ or /books/1/ and interact with these APIs directly via HTML forms.

Defining a ModelSerializer

Manually serializing data and handling object creation can become repetitive and error-prone. DRF Serializers solve this by converting complex data into JSON-friendly formats and validating incoming request data before saving it.

They act as the bridge between HTTP data and Django models.

from rest_framework import serializers
from events.models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ["id", "name", "category"]

Refactored View Using Serializer

class BookSerializerView(APIView):
    # GET /books/ or /books/<id>/
    def get(self, request, book_id=None):
        if book_id is not None:
            book = get_object_or_404(Book, id=book_id)
            serializer = BookSerializer(book)
            return Response(serializer.data)

        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

    # POST /books/
    def post(self, request):
        serializer = BookSerializer(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
        )

    # PUT /books/<id>/
    def put(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        serializer = BookSerializer(book, data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)

        return Response(
            serializer.errors,
            status=status.HTTP_400_BAD_REQUEST
        )

    # DELETE /books/<id>/
    def delete(self, request, book_id):
        book = get_object_or_404(Book, id=book_id)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

You will notice that serialize_response() method is not used anymore, which is handled by BookSerializer.

The urls stay the same, we just change BookView to BookSerializerView.

path("books/<int:book_id>/", BookSerializerView.as_view(), name='book-list'),
path("books/", BookSerializerView.as_view(), name='book-details')

GenericAPIView and Mixins

To reduce boilerplate further, DRF provides GenericAPIView along with mixins that implement common CRUD behavior. GenericAPIView adds support for:

  • queryset
  • serializer_class
  • pagination_class
  • filter_backends

When combined with mixins, it provides reusable, well-tested implementations of CRUD operations, resulting in cleaner and more maintainable code.

List & Create View

class BookListCreateView(
    mixins.ListModelMixin,
    mixins.CreateModelMixin,
    generics.GenericAPIView
):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

Retrieve, Update & Delete View

class BookDetailView(
    mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,
    mixins.DestroyModelMixin,
    generics.GenericAPIView
):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_url_kwarg = "book_id"

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

DRF requires explicit HTTP method definitions (get, post, etc.) so it knows how to dispatch requests. These methods act as a bridge between HTTP verbs and mixin-provided actions. The url configuration is updated as:

path("books/<int:book_id>/", BookDetailView.as_view(), name='book-list'),
path("books/", BookListCreateView.as_view(), name='book-details')