Django REST Framework Overview

Module 18: Python Backend - Django

What is Django REST Framework?

Django REST Framework (DRF) is a powerful and flexible toolkit for building Web APIs with Django. It's a third-party package that extends Django's capabilities, making it easy to build, test, and maintain RESTful APIs.

DRF has become the de facto standard for building APIs in Django projects, used by companies like Mozilla, Red Hat, and Eventbrite to power their web services.

Analogy: The Restaurant Kitchen Transformation

Think of Django as a traditional restaurant kitchen that serves plated meals (HTML pages) to diners seated in the restaurant. It's excellent at preparing and presenting complete dishes directly to customers.

Django REST Framework transforms this kitchen into a modern food preparation facility that can:

  • Package meals for delivery services (mobile apps)
  • Supply ingredients to other restaurants (third-party services)
  • Offer meal kits for home assembly (frontend frameworks)
  • Still serve traditional dining customers when needed

Just as the modern kitchen expands its reach beyond in-house diners, DRF extends Django's capabilities beyond serving HTML to browsers, allowing it to communicate with various clients in a standardized format.

Understanding REST and APIs

Before diving into DRF, let's clarify what REST is and why it matters:

REST (Representational State Transfer) is an architectural style for designing networked applications. RESTful APIs use HTTP requests to perform CRUD operations (Create, Read, Update, Delete) on resources, represented as URLs.

Key Principles of REST:

graph LR A[Client] -- HTTP Request --> B[API] B -- JSON/XML Response --> A B -- CRUD Operations --> C[Database]

Why Use Django REST Framework?

While you could build an API with plain Django, DRF offers significant advantages:

Real-World Usage Scenarios

Django REST Framework excels in various scenarios:

Installation and Setup

Getting started with DRF is straightforward:

1. Install Django REST Framework


pip install djangorestframework
            

2. Update settings.py


# settings.py
INSTALLED_APPS = [
    # Django apps...
    'rest_framework',
    'your_app',
]

# Optional: Default DRF settings
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}
            

3. Add DRF URLs (optional for browsable API)


# urls.py
from django.urls import path, include

urlpatterns = [
    # Other URL patterns...
    path('api-auth/', include('rest_framework.urls')),
]
            

Core Components of Django REST Framework

graph TD A[Request] --> B[View/ViewSet] B <--> C[Serializer] C <--> D[Model] B --> E[Response] F[Authentication] --> B G[Permissions] --> B H[Throttling] --> B I[Content Negotiation] --> B J[Pagination] --> B

Serializers

Serializers convert complex Django models to Python data types that can be easily rendered into JSON, XML, or other content types. They also handle deserialization, converting parsed data back into complex types.

Views & ViewSets

Views handle API requests and return responses. ViewSets combine the logic for multiple related views in a single class, following REST conventions.

Routers

Routers automatically generate URL patterns for ViewSets, ensuring consistent URL structures across your API.

Authentication & Permissions

Authentication identifies users; permissions determine what they can do. DRF provides flexible systems for both.

Building Your First Serializer

Let's start with a simple model and create a serializer for it:


# models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    published_date = models.DateField()
    isbn = models.CharField(max_length=13, unique=True)
    
    def __str__(self):
        return self.title
            

Now, let's create a serializer for this model:


# serializers.py
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'published_date', 'isbn']
            

This simple serializer will:

Testing the Serializer in Shell


# In Django shell (python manage.py shell)
from myapp.models import Book
from myapp.serializers import BookSerializer
from rest_framework.renderers import JSONRenderer
import datetime

# Create a book instance
book = Book(
    title="Django for Beginners",
    author="William S. Vincent",
    published_date=datetime.date(2020, 1, 15),
    isbn="9781735467207"
)
book.save()

# Serialize the book
serializer = BookSerializer(book)
serializer.data
# Output: {'id': 1, 'title': 'Django for Beginners', 'author': 'William S. Vincent', 'published_date': '2020-01-15', 'isbn': '9781735467207'}

# Convert to JSON
json_data = JSONRenderer().render(serializer.data)
print(json_data)
# Output: b'{"id":1,"title":"Django for Beginners","author":"William S. Vincent","published_date":"2020-01-15","isbn":"9781735467207"}'
            

Creating Your First API View

Let's create a simple API view to expose our Book model:


# views.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer

@api_view(['GET', 'POST'])
def book_list(request):
    """
    List all books, or create a new book.
    """
    if request.method == 'GET':
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)
    
    elif request.method == 'POST':
        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)

@api_view(['GET', 'PUT', 'DELETE'])
def book_detail(request, pk):
    """
    Retrieve, update or delete a book.
    """
    try:
        book = Book.objects.get(pk=pk)
    except Book.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
    
    if request.method == 'GET':
        serializer = BookSerializer(book)
        return Response(serializer.data)
    
    elif request.method == 'PUT':
        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)
    
    elif request.method == 'DELETE':
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
            

Setting Up URLs


# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('books/', views.book_list, name='book-list'),
    path('books//', views.book_detail, name='book-detail'),
]
            

With just these few files, we now have a fully functional API that can:

The Browsable API

One of the most distinctive features of DRF is its browsable API. When you access your API endpoints through a web browser, DRF renders a user-friendly HTML interface for exploring and testing your API.

Features of the Browsable API:

sequenceDiagram participant Browser participant BrowsableAPI participant JSONRenderer participant ViewFunction participant Serializer participant Model Browser->>BrowsableAPI: GET /api/books/ BrowsableAPI->>ViewFunction: Pass request ViewFunction->>Model: Query data Model-->>ViewFunction: Return queryset ViewFunction->>Serializer: Serialize data Serializer-->>ViewFunction: Return serialized data ViewFunction-->>BrowsableAPI: Return Response object BrowsableAPI->>Browser: Return HTML representation

The browsable API is invaluable during development, allowing you to:

Class-Based Views in DRF

While function-based views work well, class-based views offer more structure and reusability. DRF provides several view classes that follow RESTful design patterns:

APIView

The base class for all DRF class-based views:


# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer

class BookList(APIView):
    """
    List all books, or create a new book.
    """
    def get(self, request, format=None):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)
    
    def post(self, request, format=None):
        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)

class BookDetail(APIView):
    """
    Retrieve, update or delete a book instance.
    """
    def get_object(self, pk):
        try:
            return Book.objects.get(pk=pk)
        except Book.DoesNotExist:
            raise Http404
    
    def get(self, request, pk, format=None):
        book = self.get_object(pk)
        serializer = BookSerializer(book)
        return Response(serializer.data)
    
    def put(self, request, pk, format=None):
        book = self.get_object(pk)
        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)
    
    def delete(self, request, pk, format=None):
        book = self.get_object(pk)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
            

Generic Views

DRF provides generic views for common patterns, reducing boilerplate:


# views.py
from rest_framework import generics
from .models import Book
from .serializers import BookSerializer

class BookList(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
            

Generic views handle all the standard CRUD operations with minimal code, following best practices automatically.

URL Patterns for Class-Based Views

Connecting class-based views to URLs requires the as_view() method:


# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('books/', views.BookList.as_view(), name='book-list'),
    path('books//', views.BookDetail.as_view(), name='book-detail'),
]
            

URL Naming Conventions

RESTful APIs typically follow these URL patterns:

URL Pattern HTTP Method Action URL Name
/books/ GET List all books book-list
/books/ POST Create a new book book-list
/books/{id}/ GET Retrieve a book book-detail
/books/{id}/ PUT Update a book book-detail
/books/{id}/ DELETE Delete a book book-detail

Following these conventions ensures your API is intuitive and predictable for consumers.

Real-World Example: Library API

Let's expand our book example to a more complete library API with related models:

Models


# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    biography = models.TextField(blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    
    def __str__(self):
        return self.name

class Genre(models.Model):
    name = models.CharField(max_length=50)
    
    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    summary = models.TextField()
    isbn = models.CharField(max_length=13, unique=True)
    published_date = models.DateField()
    genres = models.ManyToManyField(Genre, related_name='books')
    
    def __str__(self):
        return self.title
            

Serializers


# serializers.py
from rest_framework import serializers
from .models import Author, Genre, Book

class GenreSerializer(serializers.ModelSerializer):
    class Meta:
        model = Genre
        fields = ['id', 'name']

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name', 'biography', 'date_of_birth']

class BookSerializer(serializers.ModelSerializer):
    # Nested serialization for related fields
    author = AuthorSerializer(read_only=True)
    author_id = serializers.PrimaryKeyRelatedField(
        queryset=Author.objects.all(),
        source='author',
        write_only=True
    )
    genres = GenreSerializer(many=True, read_only=True)
    genre_ids = serializers.PrimaryKeyRelatedField(
        queryset=Genre.objects.all(),
        source='genres',
        write_only=True,
        many=True
    )
    
    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'author_id', 'summary', 'isbn', 'published_date', 'genres', 'genre_ids']
            

Views


# views.py
from rest_framework import generics
from .models import Author, Genre, Book
from .serializers import AuthorSerializer, GenreSerializer, BookSerializer

class AuthorList(generics.ListCreateAPIView):
    queryset = Author.objects.all()
    serializer_class = AuthorSerializer

class AuthorDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Author.objects.all()
    serializer_class = AuthorSerializer

class GenreList(generics.ListCreateAPIView):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer

class GenreDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer

class BookList(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
            

URLs


# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('authors/', views.AuthorList.as_view(), name='author-list'),
    path('authors//', views.AuthorDetail.as_view(), name='author-detail'),
    path('genres/', views.GenreList.as_view(), name='genre-list'),
    path('genres//', views.GenreDetail.as_view(), name='genre-detail'),
    path('books/', views.BookList.as_view(), name='book-list'),
    path('books//', views.BookDetail.as_view(), name='book-detail'),
]
            

This API provides complete CRUD operations for authors, genres, and books, with proper handling of relationships between them.

Beyond the Basics

In upcoming lectures, we'll explore more advanced DRF features:

Practice Activities

  1. Basic API Creation: Create a simple model (e.g., Todo, Note, or Product) and build a complete API for it using function-based views. Test your API using the browsable API interface.
  2. Class-Based Views: Refactor your function-based views to use class-based views. Compare the two approaches and note the differences in code organization and readability.
  3. Related Models: Add a related model to your project (e.g., Category for Products) and implement nested serialization to display the relationship correctly.
  4. API Testing: Write a Python script using the requests library to interact with your API programmatically, performing each CRUD operation.

Key Takeaways

Django REST Framework transforms Django into a powerful platform for building APIs that can serve multiple client types, from mobile apps to JavaScript frontends. In the next lecture, we'll explore serializers in more depth, learning how to handle complex data structures and relationships.