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

🧠 Test Your Knowledge

Which ViewSet provides full CRUD operations?