APIViews & ViewSets

Difficulty: Advanced

DRF provides several view classes at different abstraction levels. APIView is the base class that adds DRF features (authentication, permissions, content negotiation) to Django's View. GenericAPIView adds queryset and serializer handling. Concrete view classes (ListAPIView, RetrieveAPIView, etc.) add specific behavior. ViewSets combine multiple view actions into a single class.

APIView is the DRF equivalent of Django's View. You define get(), post(), put(), patch(), delete() methods. It provides request parsing (request.data instead of request.POST), response rendering (Response instead of HttpResponse), authentication, permission checking, and exception handling.

GenericAPIView extends APIView with queryset and serializer_class attributes. Combined with mixins (ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin), it provides standard CRUD operations. Concrete generic views combine GenericAPIView with specific mixins.

ViewSets are the highest abstraction level. Instead of defining separate views for list, create, retrieve, update, and delete, a ViewSet handles all of them in a single class. ModelViewSet provides complete CRUD functionality with minimal code - just set queryset and serializer_class.

ViewSets use 'actions' instead of HTTP method handlers: list() (GET /), create() (POST /), retrieve() (GET /{pk}/), update() (PUT /{pk}/), partial_update() (PATCH /{pk}/), destroy() (DELETE /{pk}/). The Router (covered in the next lesson) maps these actions to URL patterns automatically.

You can restrict which actions a ViewSet supports using mixins. Instead of ModelViewSet (which includes all CRUD), you can mix specific mixins: CreateModelMixin + ListModelMixin + GenericViewSet gives you only create and list operations.

Customizing ViewSet behavior follows the same patterns as generic views: override get_queryset(), get_serializer_class(), perform_create(), perform_update(), and perform_destroy() for custom logic.

Code examples

APIView (Low-Level)

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Article
from .serializers import ArticleSerializer

class ArticleListView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ArticleSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(author=request.user)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

class ArticleDetailView(APIView):
    def get(self, request, pk):
        article = get_object_or_404(Article, pk=pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    def put(self, request, pk):
        article = get_object_or_404(Article, pk=pk)
        serializer = ArticleSerializer(article, data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        article = get_object_or_404(Article, pk=pk)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

APIView gives full control over each HTTP method. request.data handles JSON, form data, and multipart. Response auto-negotiates content type. raise_exception=True auto-returns 400 on validation errors.

Generic Views

from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer

# List + Create
class ArticleListCreateView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

# Retrieve + Update + Delete
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

# Read-only views
class ArticleListView(generics.ListAPIView):
    queryset = Article.objects.filter(is_published=True)
    serializer_class = ArticleSerializer

class ArticleRetrieveView(generics.RetrieveAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    lookup_field = 'slug'  # Use slug instead of pk

Generic views provide pre-built combinations: ListCreateAPIView (list + create), RetrieveUpdateDestroyAPIView (detail + edit + delete). perform_create() is the hook for setting non-form fields. lookup_field customizes the URL parameter.

ModelViewSet

from rest_framework import viewsets, permissions
from .models import Article
from .serializers import ArticleSerializer, ArticleDetailSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return ArticleDetailSerializer
        return ArticleSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        if self.action == 'list':
            return qs.filter(is_published=True)
        return qs

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    def perform_update(self, serializer):
        serializer.save(edited_by=self.request.user)

ModelViewSet provides all CRUD actions in a single class. Override get_serializer_class() for action-specific serializers, get_queryset() for action-specific filtering, and perform_create/update for custom save logic.

Custom ViewSet with Mixins

from rest_framework import viewsets, mixins
from .models import Tag
from .serializers import TagSerializer

# Read-only ViewSet (list + retrieve only)
class TagViewSet(
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet,
):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer

# Create + List ViewSet (no update or delete)
class CommentViewSet(
    mixins.CreateModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet,
):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

# Or use ReadOnlyModelViewSet
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

Mix specific mixins with GenericViewSet to control which actions are available. ReadOnlyModelViewSet provides list + retrieve. This gives fine-grained control over API capabilities.

Key points

Concepts covered

APIView, GenericAPIView, ViewSet, ModelViewSet, Mixins