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:
- Stateless: Each request contains all information needed to complete it
- Client-Server: Separation of concerns between user interface and data storage
- Uniform Interface: Standardized way to interact with resources
- Resource-Based: Everything is a resource, identified by URLs
- Representation-Oriented: Resources can have multiple representations (JSON, XML, etc.)
Why Use Django REST Framework?
While you could build an API with plain Django, DRF offers significant advantages:
- Serialization: Effortlessly convert complex data like querysets and model instances to native Python datatypes
- Authentication: Out-of-the-box support for OAuth1, OAuth2, token authentication, etc.
- Permissions: Fine-grained control over access to API endpoints
- Viewsets & Routers: Quickly build consistent RESTful API patterns
- Browsable API: Human-friendly HTML output for API exploration
- Throttling: Rate limiting to protect your APIs from overuse
- Content Negotiation: Support multiple formats like JSON, XML, etc.
Real-World Usage Scenarios
Django REST Framework excels in various scenarios:
- Mobile Backends: Provide data for iOS and Android apps
- Single Page Applications (SPAs): Power React, Vue, or Angular frontends
- Public APIs: Create developer-facing interfaces to your data
- IoT Integration: Connect smart devices to your Django application
- Microservices: Build independent services in a distributed architecture
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
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:
- Generate fields automatically based on the model
- Handle serialization (model to JSON) and deserialization (JSON to model)
- Perform validation based on model constraints
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:
- List all books (GET /books/)
- Create a new book (POST /books/)
- Retrieve a single book (GET /books/1/)
- Update a book (PUT /books/1/)
- Delete a book (DELETE /books/1/)
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:
- Interactive Documentation: Lists available endpoints and methods
- Forms: Auto-generated forms for POST, PUT requests
- Authentication: Login/logout capabilities
- Response Format Selection: Switch between JSON, API, XML
The browsable API is invaluable during development, allowing you to:
- Test your API without external tools like Postman
- Debug request and response data
- Understand how your API behaves
- Demonstrate functionality to stakeholders
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:
- ViewSets & Routers: Higher-level abstractions for standard REST patterns
- Authentication & Permissions: Securing your API with various strategies
- Pagination: Handling large datasets
- Filtering & Searching: Enabling clients to narrow results
- Advanced Serialization: Custom fields, validation, and nested relationships
Practice Activities
- 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.
- 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.
- Related Models: Add a related model to your project (e.g., Category for Products) and implement nested serialization to display the relationship correctly.
-
API Testing: Write a Python script using the
requestslibrary to interact with your API programmatically, performing each CRUD operation.
Key Takeaways
- Django REST Framework significantly simplifies building RESTful APIs with Django
- Serializers convert between Django models and Python data types that can be rendered to JSON
- Views (function or class-based) handle HTTP methods and return appropriate responses
- The browsable API provides a user-friendly interface for exploring and testing your API
- Following RESTful conventions makes your API intuitive and predictable for consumers
- DRF's class-based views reduce boilerplate and enforce best practices
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.