Views and Routing
DRF builds a complete view class hierarchy on top of Django’s native views, from the low-level APIView to the high-level ModelViewSet, encapsulating repetitive logic at each level to reduce boilerplate code. This article starts with Request / Response objects, then covers base view classes, generic views, Mixin classes, ViewSets, and Routers in full.
Request and Response
Request Object
DRF replaces Django’s native HttpRequest with rest_framework.request.Request. The most important extensions are:
request.data: Parsed request body; supports JSON, forms, and files; accessed uniformly as a dictionary (replacesrequest.POST).request.query_params: URL query string parameters (replacesrequest.GET).request.user: The authenticated user object.request.auth: Authentication credential (e.g., the Token value).
from rest_framework.views import APIView
from rest_framework.response import Response
class DemoView(APIView):
def get(self, request):
# Get URL parameter: /api/items/?page=2
page = request.query_params.get("page", 1)
return Response({"page": page})
def post(self, request):
# Get request body (JSON or form both work)
name = request.data.get("name")
return Response({"received": name})Response Object
rest_framework.response.Response automatically selects the rendering format based on the client’s Accept header (returns an HTML page for browsers, JSON for other clients).
Response(data, status=None, headers=None, content_type=None)data: Python basic types (dict, list); cannot be model objects.status: HTTP status code; recommended to use constants fromrest_framework.status.
from rest_framework import status
from rest_framework.response import Response
return Response({"message": "created"}, status=status.HTTP_201_CREATED)
return Response(None, status=status.HTTP_204_NO_CONTENT)Common status code constants: HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND.
APIView
APIView is the root base class for all DRF views, inheriting from Django’s View, with additional features:
- Automatic request body parsing (JSON / form).
- Automatic response rendering (JSON / browser page).
- Unified catching and formatting of
APIExceptionexceptions. - Executing authentication, permission checks, and throttling before
dispatch().
Usage is the same as Django’s View — define methods corresponding to HTTP methods:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from students.models import Student
from .serializers import StudentModelSerializer
class StudentsAPIView(APIView):
"""List endpoint: get all students / create a student"""
def get(self, request):
students = Student.objects.all()
serializer = StudentModelSerializer(instance=students, many=True)
return Response(serializer.data)
def post(self, request):
serializer = StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
return Response(StudentModelSerializer(instance).data, status=status.HTTP_201_CREATED)
class StudentAPIView(APIView):
"""Detail endpoint: get / update / delete a single student"""
def get(self, request, pk):
student = Student.objects.get(pk=pk)
return Response(StudentModelSerializer(student).data)
def put(self, request, pk):
student = Student.objects.get(pk=pk)
serializer = StudentModelSerializer(instance=student, data=request.data)
serializer.is_valid(raise_exception=True)
return Response(StudentModelSerializer(serializer.save()).data)
def delete(self, request, pk):
Student.objects.get(pk=pk).delete()
return Response(None, status=status.HTTP_204_NO_CONTENT)Class attributes can configure authentication / permission / throttling policies (overriding global settings):
class StudentAPIView(APIView):
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
throttle_classes = [UserRateThrottle]GenericAPIView
GenericAPIView inherits from APIView and adds methods for working with serializers and database queries. It is the foundation for all generic views.
Core attributes:
class StudentGenericView(GenericAPIView):
queryset = Student.objects.all() # Required
serializer_class = StudentModelSerializer # Required
# pagination_class = StandardPagination # Optional
# filter_backends = [DjangoFilterBackend] # OptionalCore methods:
| Method | Description |
|---|---|
get_queryset() | Returns the queryset; can be overridden for dynamic filtering |
get_object() | Returns a single object by URL pk; auto-returns 404 if not found |
get_serializer() | Returns a serializer instance; auto-injects context (with request) |
get_serializer_class() | Returns the serializer class; override to use different serializers in one view |
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
class StudentGenericAPIView(GenericAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request):
serializer = self.get_serializer(instance=self.get_queryset(), many=True)
return Response(serializer.data)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=201)5 Mixin Classes
Mixin classes encapsulate common CRUD operations and must be used with GenericAPIView (because they call get_queryset, get_serializer, etc.).
| Mixin Class | Method provided | HTTP method | Status code |
|---|---|---|---|
ListModelMixin | list() | GET (list) | 200 |
CreateModelMixin | create() | POST | 201 |
RetrieveModelMixin | retrieve() | GET (detail) | 200 / 404 |
UpdateModelMixin | update() | PUT / PATCH | 200 |
DestroyModelMixin | destroy() | DELETE | 204 / 404 |
Example:
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
from rest_framework.generics import GenericAPIView
class StudentsView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class StudentView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request, pk):
return self.retrieve(request, pk)
def put(self, request, pk):
return self.update(request, pk)
def delete(self, request, pk):
return self.destroy(request, pk)Generic View Subclasses
DRF pre-packages common Mixin combinations so you can simply inherit them — no need to write get / post methods:
from rest_framework.generics import (
ListAPIView, CreateAPIView, RetrieveAPIView,
UpdateAPIView, DestroyAPIView,
ListCreateAPIView, RetrieveUpdateDestroyAPIView,
)| View subclass | HTTP methods provided |
|---|---|
ListAPIView | GET (list) |
CreateAPIView | POST |
RetrieveAPIView | GET (detail) |
UpdateAPIView | PUT, PATCH |
DestroyAPIView | DELETE |
ListCreateAPIView | GET (list), POST |
RetrieveUpdateAPIView | GET, PUT, PATCH |
RetrieveDestroyAPIView | GET, DELETE |
RetrieveUpdateDestroyAPIView | GET, PUT, PATCH, DELETE |
class StudentsView(ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
class StudentView(RetrieveUpdateDestroyAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializerViewSet
ViewSet combines a group of related endpoints into one class. Method names are no longer bound to HTTP methods; instead they are mapped through actions.
ViewSet and GenericViewSet
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin
class StudentViewSet(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializerWhen registering routes, explicitly specify the mapping between HTTP methods and actions:
from django.urls import path, re_path
urlpatterns = [
path("students/", StudentViewSet.as_view({"get": "list", "post": "create"})),
re_path(r"students/(?P<pk>\d+)/", StudentViewSet.as_view({"get": "retrieve"})),
]ModelViewSet
ModelViewSet has all 5 Mixins built in and is the most concise full-CRUD solution:
from rest_framework.viewsets import ModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializerCustom Actions
Use the @action decorator to add non-standard operations to a ViewSet:
from rest_framework.decorators import action
from rest_framework.response import Response
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
@action(methods=["get"], detail=False)
def top5(self, request):
"""Get the 5 most recently added students (path: /students/top5/)"""
qs = self.get_queryset().order_by("-id")[:5]
return Response(self.get_serializer(qs, many=True).data)
@action(methods=["post"], detail=True)
def reset_password(self, request, pk):
"""Reset a specific student's password (path: /students/{pk}/reset_password/)"""
# detail=True means the action targets a single record; URL includes pk
...
return Response({"message": "Password has been reset"})detail=False: Path is/students/top5/detail=True: Path is/students/{pk}/reset_password/
Inside view methods, access the current action name via self.action. This is useful in methods like get_serializer_class() to return different serializers based on the action.
Router
The Router automatically generates RESTful routes for ViewSet classes — no need to write urlpatterns manually.
Usage
# students/urls.py
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("students", views.StudentModelViewSet)
# router.register("prefix", ViewSetClass, basename="...")
urlpatterns = router.urlsOr append the generated routes to an existing list:
urlpatterns = [
path("other/", other_view),
]
urlpatterns += router.urlsDefaultRouter vs SimpleRouter
| Feature | SimpleRouter | DefaultRouter |
|---|---|---|
| List / detail routes | Yes | Yes |
| Root API listing | No | Yes (visit / to see all endpoints) |
Both are usable in production; DefaultRouter is more convenient for development since it shows all registered routes.
Routes automatically generated by DefaultRouter:
GET /students/ → list
POST /students/ → create
GET /students/{pk}/ → retrieve
PUT /students/{pk}/ → update
PATCH /students/{pk}/ → partial_update
DELETE /students/{pk}/ → destroyCustom actions declared with @action are also automatically included by the Router:
GET /students/top5/ → top5
POST /students/{pk}/reset_password/ → reset_passwordChoosing the Right View Class
Choose the appropriate level based on interface complexity:
| Scenario | Recommended approach |
|---|---|
| Standard CRUD, no customization | ModelViewSet + Router |
| Partial CRUD (e.g., read-only) | ReadOnlyModelViewSet or Mixin combination |
| Same URL needs multiple serializers | Override get_serializer_class() |
| Interface unrelated to models (aggregation, etc.) | APIView / GenericAPIView |
| Custom queryset (e.g., filter by user) | Override get_queryset() |