Django REST Framework ViewSets
Simplifying API development with ViewSets
📦 What are ViewSets?
ViewSets combine the logic for multiple related views into a single class. They automatically handle list, create, retrieve, update, and delete operations, dramatically reducing code duplication and development time.
# Complete CRUD API in just 3 lines!
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
This creates endpoints for:
GET /books/ - List all books
POST /books/ - Create book
GET /books/{id}/ - Retrieve book
PUT /books/{id}/ - Update book
DELETE /books/{id}/ - Delete book
ViewSet Types
ViewSet
Base class for custom logic
class BookViewSet(ViewSet):
def list(self, request):
pass
GenericViewSet
Combine with mixins
class BookViewSet(
mixins.ListModelMixin,
GenericViewSet):
pass
ReadOnlyModelViewSet
List and retrieve only
class BookViewSet(
ReadOnlyModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
ModelViewSet
Full CRUD operations
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
🔹 ModelViewSet
The most powerful ViewSet that provides complete CRUD functionality. Perfect for standard REST APIs with minimal code:
# views.py
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.
Provides `list`, `create`, `retrieve`, `update`, and `destroy` actions.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')
urlpatterns = [
path('api/', include(router.urls)),
]
Generated URLs:
GET /api/books/ - List books
POST /api/books/ - Create book
GET /api/books/{id}/ - Get book detail
PUT /api/books/{id}/ - Update book (full)
PATCH /api/books/{id}/ - Update book (partial)
DELETE /api/books/{id}/ - Delete book
🔹 ReadOnlyModelViewSet
Use when you only need read operations without create, update, or delete functionality. Great for public APIs:
# views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ReadOnlyModelViewSet):
"""
A ViewSet that provides only `list` and `retrieve` actions.
Users can view books but cannot modify them.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
# Optional: Add filtering
def get_queryset(self):
queryset = Book.objects.all()
author = self.request.query_params.get('author')
if author:
queryset = queryset.filter(author__icontains=author)
return queryset
Available Endpoints:
GET /api/books/ - List all books
GET /api/books/{id}/ - Get book detail
GET /api/books/?author=John - Filter by author
🔹 Custom ViewSet Actions
Add custom endpoints to your ViewSet using the @action decorator for additional functionality beyond standard CRUD:
# views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# Custom list action: /api/books/published/
@action(detail=False, methods=['get'])
def published(self, request):
"""Get only published books"""
published_books = Book.objects.filter(is_published=True)
serializer = self.get_serializer(published_books, many=True)
return Response(serializer.data)
# Custom detail action: /api/books/{id}/publish/
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""Publish a specific book"""
book = self.get_object()
book.is_published = True
book.save()
return Response({'status': 'book published'})
# Custom action with parameters
@action(detail=False, methods=['get'])
def by_author(self, request):
"""Get books by author name"""
author = request.query_params.get('name')
books = Book.objects.filter(author__icontains=author)
serializer = self.get_serializer(books, many=True)
return Response(serializer.data)
Custom Endpoints:
GET /api/books/published/ - List published books
POST /api/books/{id}/publish/ - Publish a book
GET /api/books/by_author/?name=John - Books by author
🔹 ViewSet with Mixins
Create custom ViewSets by combining GenericViewSet with specific mixins for fine-grained control over available operations:
# views.py
from rest_framework import viewsets, mixins
from .models import Book
from .serializers import BookSerializer
class BookViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet):
"""
A ViewSet that provides list, retrieve, and create actions.
Update and delete are not available.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
def perform_create(self, serializer):
# Custom create logic
serializer.save(created_by=self.request.user)
Available Mixins:
- ListModelMixin: list() method
- CreateModelMixin: create() method
- RetrieveModelMixin: retrieve() method
- UpdateModelMixin: update() and partial_update() methods
- DestroyModelMixin: destroy() method
🔹 Customizing ViewSet Behavior
Override methods to customize how your ViewSet handles requests and data:
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
# Custom queryset based on user
def get_queryset(self):
user = self.request.user
if user.is_staff:
return Book.objects.all()
return Book.objects.filter(is_published=True)
# Different serializers for different actions
def get_serializer_class(self):
if self.action == 'list':
return BookListSerializer
return BookDetailSerializer
# Custom create behavior
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# Custom update behavior
def perform_update(self, serializer):
serializer.save(updated_by=self.request.user)
# Custom delete behavior
def perform_destroy(self, instance):
instance.is_active = False
instance.save() # Soft delete
🔹 Multiple ViewSets Example
Organize related resources with multiple ViewSets in a single application:
# views.py
from rest_framework import viewsets
from .models import Book, Author, Category
from .serializers import BookSerializer, AuthorSerializer, CategorySerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
# urls.py
from rest_framework.routers import DefaultRouter
from .views import BookViewSet, AuthorViewSet, CategoryViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')
router.register(r'authors', AuthorViewSet, basename='author')
router.register(r'categories', CategoryViewSet, basename='category')
urlpatterns = router.urls
Generated API Structure:
/api/books/ - Book CRUD operations /api/authors/ - Author CRUD operations /api/categories/ - Category read-only operations