Django REST Views
In this tutorial, we'll explore the various view implementations available in Django REST Framework (DRF) for building powerful APIs. Views are a crucial component in any REST API as they handle HTTP requests and return appropriate responses.
Introduction to DRF Views
Views in Django REST Framework are responsible for processing incoming HTTP requests and returning HTTP responses. They encapsulate the logic for:
- Parsing request data
- Validating the data using serializers
- Performing database operations
- Formatting and returning responses
DRF provides several ways to implement views, ranging from function-based views to highly abstracted viewsets. The right choice depends on your specific requirements and how much abstraction you need.
Types of Views in DRF
Let's explore the main types of views available in Django REST Framework:
- Function-based views
- Class-based views (APIView)
- Generic views
- ViewSets and Routers
Function-Based Views
Function-based views are the simplest way to create API endpoints. They're ideal when you need complete control over the request handling logic.
How to Create Function-Based Views
First, import the necessary modules:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer
Then, create a function-based view:
@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)
The @api_view
decorator specifies which HTTP methods the view should respond to.
Example Request/Response
For a GET request to /api/books/
:
Response (200 OK):
[
{
"id": 1,
"title": "Django for Beginners",
"author": "William S. Vincent",
"published_date": "2020-01-01"
},
{
"id": 2,
"title": "Django for APIs",
"author": "William S. Vincent",
"published_date": "2020-06-01"
}
]
For a POST request to /api/books/
:
Request Body:
{
"title": "Django for Professionals",
"author": "William S. Vincent",
"published_date": "2021-01-01"
}
Response (201 Created):
{
"id": 3,
"title": "Django for Professionals",
"author": "William S. Vincent",
"published_date": "2021-01-01"
}
Class-Based Views (APIView)
Class-based views provide a more organized way to handle different HTTP methods. DRF's APIView
class is a subclass of Django's View
class with some additional features.
How to Create a Class-Based View
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):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
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)
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):
book = self.get_object(pk)
serializer = BookSerializer(book)
return Response(serializer.data)
def put(self, request, pk):
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):
book = self.get_object(pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
To wire these up in your urls.py
:
from django.urls import path
from . import views
urlpatterns = [
path('books/', views.BookList.as_view()),
path('books/<int:pk>/', views.BookDetail.as_view()),
]
Generic Class-Based Views
Generic views provide pre-implemented behavior for common use cases. They help reduce boilerplate code and keep your views clean.
Common Generic Views
- ListAPIView: For read-only endpoints that represent a collection of model instances
- CreateAPIView: For endpoints that create model instances
- RetrieveAPIView: For read-only endpoints that represent a single model instance
- UpdateAPIView: For endpoints that update model instances
- DestroyAPIView: For endpoints that delete model instances
- ListCreateAPIView: For read-write endpoints that represent a collection of model instances
- RetrieveUpdateDestroyAPIView: For read-write-delete endpoints that represent a single model instance
Here's how to implement the same book API using generic views:
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
That's it! Just two simple classes to handle all CRUD operations. DRF's generic views handle the implementation details for you.
Customizing Generic Views
You can customize generic views by overriding their methods:
class BookList(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_queryset(self):
"""
Optionally restricts the returned books,
by filtering against a `genre` query parameter in the URL.
"""
queryset = Book.objects.all()
genre = self.request.query_params.get('genre', None)
if genre is not None:
queryset = queryset.filter(genre=genre)
return queryset
def perform_create(self, serializer):
"""
Associate the current user with the book being created
"""
serializer.save(owner=self.request.user)
ViewSets and Routers
ViewSets and routers represent the highest level of abstraction in DRF. They are particularly useful when you want to perform standard CRUD operations.
Creating a ViewSet
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing book instances.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
The ModelViewSet
class automatically provides .list()
, .retrieve()
, .create()
, .update()
, .partial_update()
, and .destroy()
actions.
Setting Up Routers
Routers automatically generate URL patterns for your ViewSets:
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = router.urls
This router will generate the following URL patterns:
GET /books/
- list all booksPOST /books/
- create a new bookGET /books/{pk}/
- retrieve a specific bookPUT /books/{pk}/
- update a specific bookPATCH /books/{pk}/
- partially update a specific bookDELETE /books/{pk}/
- delete a specific book
Practical Example: Building a Blog API
Let's consolidate our knowledge by building a simplified blog API. We'll implement endpoints for posts and comments.
First, create the models:
# models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f'Comment by {self.author.username} on {self.post.title}'
Next, create serializers:
# serializers.py
from rest_framework import serializers
from .models import Post, Comment
from django.contrib.auth.models import User
class CommentSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
class Meta:
model = Comment
fields = ['id', 'text', 'created_at', 'author', 'post']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'content', 'created_at', 'author', 'comments']
Now, implement the ViewSets:
# views.py
from rest_framework import viewsets, permissions
from .models import Post, Comment
from .serializers import PostSerializer, CommentSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Finally, configure the URLs using a router:
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'posts', views.PostViewSet)
router.register(r'comments', views.CommentViewSet)
urlpatterns = [
path('', include(router.urls)),
]
This gives us a complete API with endpoints for both posts and comments, with minimal code.
Summary
Django REST Framework offers a spectrum of view implementations, from simple function-based views to highly abstracted ViewSets:
- Function-based views: Best for simple use cases or custom behavior
- Class-based views (APIView): More structured approach for handling HTTP methods
- Generic views: Pre-built functionality for common patterns
- ViewSets and Routers: Highest level of abstraction, ideal for standard CRUD operations
When choosing which type to use, consider:
- The complexity of your API
- How much customization you need
- How closely your endpoints align with standard REST patterns
- How much code duplication you're willing to accept
Generally, it's best to use the most abstracted option that still meets your needs, but don't hesitate to mix different types in the same project where appropriate.
Additional Resources
- Django REST Framework Official Documentation on Views
- Django REST Framework Official Documentation on Generic Views
- Django REST Framework Official Documentation on ViewSets
- Django REST Framework Official Documentation on Routers
Exercises
- Create a function-based view that returns a list of all books published in a specific year (passed as a query parameter).
- Implement a class-based view for a
Category
model that returns all books in that category. - Create a generic view that lists all books by a specific author.
- Implement a ViewSet for an
Author
model with custom actions to:- List top-rated authors
- Get the total number of books written by an author
- Extend the blog API example to include user authentication and ensure users can only edit their own posts and comments.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)