DRF: Views & ViewSets
A serializer knows how to shape data, but nothing yet decides what happens when a request actually arrives at a URL. That's the job of a DRF view — and DRF gives you a spectrum of options here, from full manual control at one end to an almost automatic shortcut at the other.
APIView: The REST Equivalent of a Plain CBV
APIView is DRF's own base class, built on the same idea as the class-based views from
Class-Based Views — one method per HTTP verb — but aimed
at APIs specifically. Instead of returning a template with render(), an
APIView method returns a DRF Response object built from serialized data:
# taskflow/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Task
from .serializers import TaskSerializer
class TaskListAPIView(APIView):
def get(self, request):
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
def post(self, request):
serializer = TaskSerializer(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)
get() handles listing every task — note many=True, which tells the
serializer it's handling a queryset of several objects rather than one. post() handles
creating a new task, following the exact same validate-then-save pattern from the previous lesson.
Every response detail — what gets queried, what status code comes back, what the error response
looks like — is written out explicitly, which is precisely the appeal of APIView: full
control, at the cost of writing that control out by hand.
ViewSet: Bundling list/create/retrieve/update/delete Into One Class
A huge share of real API endpoints follow the exact same shape: list all objects, create one,
retrieve one, update one, delete one — the same five operations, over and over, for model after
model. Writing a separate APIView for each of those five actions, for every model, gets
repetitive fast. A ModelViewSet collapses all five into a single class:
# taskflow/views.py
from rest_framework import viewsets
from .models import Task
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
That's genuinely the whole class — two lines of actual configuration — and it already supports
listing, creating, retrieving one task, updating one, and deleting one. Compare that to the
TaskListAPIView above, which only covered two of those five actions in roughly a dozen
lines; a ModelViewSet covering all five with equivalent hand-written
APIView code would run several times longer.
Wiring a ViewSet's URLs With a Router
A ViewSet doesn't map onto a single URL the way a normal view does — it needs several
related URL patterns (the list endpoint, the detail endpoint, and so on). DRF's
router generates all of them automatically from one registration line:
# taskflow/urls.py
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet
router = DefaultRouter()
router.register('tasks', TaskViewSet)
urlpatterns = router.urls
That single router.register('tasks', TaskViewSet) call produces the standard set of
REST URL patterns on its own — /tasks/ for listing and creating, and
/tasks/<pk>/ for retrieving, updating, and deleting one task — without writing out
a single path() call by hand. It's the same kind of convenience win
ModelViewSet offers for view logic, applied to URL configuration instead.
APIView vs ModelViewSet: An Honest Comparison
APIView | ModelViewSet | |
|---|---|---|
| Control over each endpoint | Full — every line of behavior is explicit | Limited by default — you're accepting the standard CRUD behavior |
| Amount of code for standard CRUD | A lot — one method per action, repeated per model | Minimal — two or three attribute lines typically covers it |
| URL wiring | Manual path() entries, one per action | Automatic, via a router |
| Best fit | Endpoints with genuinely custom logic that doesn't map onto plain CRUD | Straightforward CRUD over a single model, which covers most API endpoints in practice |
Generic API Views: A Middle Ground
Between those two extremes sit DRF's generic views — classes like
ListAPIView and RetrieveAPIView, which handle exactly one action each
(just listing, or just retrieving one object) rather than all five. They're worth knowing exist:
reach for one when an endpoint should support just reading a list or just reading a single object,
and nothing else — a read-only reporting endpoint is a typical example — without either writing a
full APIView by hand or pulling in every action a ModelViewSet provides.
TaskViewSet is wide open — anyone can create,
edit, or delete any task. DRF: Authentication &
Permissions covers locking that down properly.