Skip to content

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 (replaces request.POST).
  • request.query_params: URL query string parameters (replaces request.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 from rest_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 APIException exceptions.
  • 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]  # Optional

Core methods:

MethodDescription
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 ClassMethod providedHTTP methodStatus code
ListModelMixinlist()GET (list)200
CreateModelMixincreate()POST201
RetrieveModelMixinretrieve()GET (detail)200 / 404
UpdateModelMixinupdate()PUT / PATCH200
DestroyModelMixindestroy()DELETE204 / 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 subclassHTTP methods provided
ListAPIViewGET (list)
CreateAPIViewPOST
RetrieveAPIViewGET (detail)
UpdateAPIViewPUT, PATCH
DestroyAPIViewDELETE
ListCreateAPIViewGET (list), POST
RetrieveUpdateAPIViewGET, PUT, PATCH
RetrieveDestroyAPIViewGET, DELETE
RetrieveUpdateDestroyAPIViewGET, PUT, PATCH, DELETE
class StudentsView(ListCreateAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentModelSerializer

class StudentView(RetrieveUpdateDestroyAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentModelSerializer

ViewSet

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 = StudentModelSerializer

When 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 = StudentModelSerializer

Custom 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.urls

Or append the generated routes to an existing list:

urlpatterns = [
    path("other/", other_view),
]
urlpatterns += router.urls

DefaultRouter vs SimpleRouter

FeatureSimpleRouterDefaultRouter
List / detail routesYesYes
Root API listingNoYes (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}/      → destroy

Custom actions declared with @action are also automatically included by the Router:

GET  /students/top5/                → top5
POST /students/{pk}/reset_password/ → reset_password

Choosing the Right View Class

Choose the appropriate level based on interface complexity:

ScenarioRecommended approach
Standard CRUD, no customizationModelViewSet + Router
Partial CRUD (e.g., read-only)ReadOnlyModelViewSet or Mixin combination
Same URL needs multiple serializersOverride get_serializer_class()
Interface unrelated to models (aggregation, etc.)APIView / GenericAPIView
Custom queryset (e.g., filter by user)Override get_queryset()
Last updated on