skip to content

In Django REST Framework, what does GenericAPIView add on top of APIView, and how do the mixins combine into concrete views like ListCreateAPIView?

level: juniorimportance: must knowfreq 62%

answer

  1. queryset plus serializer plumbing
  2. helpers, but no handlers
  3. five action mixins
  4. concrete class binds get/post

basics

~10 s

GenericAPIView adds queryset, serializer_class, lookup_field and helpers like get_queryset(), get_serializer() and get_object(). Five mixins supply list(), create(), retrieve(), update() and destroy(); concrete classes such as ListCreateAPIView bind get() and post() to them.

solid answer

~30 s

`GenericAPIView` extends `APIView` with model plumbing: the `queryset` and `serializer_class` attributes, `lookup_field` (default `'pk'`), and helpers — `get_queryset()`, `get_serializer()`, `get_object()`, `filter_queryset()`, `paginate_queryset()`. It defines no HTTP handlers. The five mixins in `rest_framework.mixins` provide **actions** — `list()`, `create()`, `retrieve()`, `update()`/`partial_update()`, `destroy()` — written against those helpers. Concrete generics are just mixins plus `GenericAPIView` plus one-line handlers: `ListCreateAPIView` maps `get()` to `list()` and `post()` to `create()`. You customise them by overriding hooks such as `get_queryset()` or `perform_create()`, not the actions.

code

python · 30 lines
python
from rest_framework import generics, mixins

from .models import Ticket
from .serializers import TicketSerializer


class TicketDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Ticket.objects.all()
    serializer_class = TicketSerializer


# The same endpoint composed by hand from the building blocks
class TicketDetailByHand(mixins.RetrieveModelMixin,
                         mixins.UpdateModelMixin,
                         mixins.DestroyModelMixin,
                         generics.GenericAPIView):
    queryset = Ticket.objects.all()
    serializer_class = TicketSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

go deeper

for a junior

Name the layers: APIView, GenericAPIView, the five mixins, and the concrete classes. Be able to say which concrete class serves a collection URL and which serves an item URL.

for a middle

Explain that mixins provide actions, not handlers, and walk through what create() does: validate, call perform_create(), return 201 with an optional Location header.

for a senior

Show judgement about the layer: generics for plain model CRUD customised through hooks, APIView when the endpoint is a command or report and overriding would bury the generic flow.

for a principal

Frame generics as a team convention: consistent status codes, pagination and permission flow across endpoints, and a review rule for when an endpoint may leave the generic path.

## Three layers, one inheritance chain Django REST Framework (DRF) builds its class-based API views in layers. Each layer adds one kind of behaviour, and you pick the highest layer that still fits your endpoint. | Layer | Class or module | What it adds | |---|---|---| | Base view | `APIView` | Request parsing, authentication, permission and throttle checks, content negotiation, exception handling. You write `get()`, `post()` and friends yourself. | | Generic base | `GenericAPIView` | Model-oriented plumbing: `queryset`, `serializer_class`, lookups, filtering and pagination helpers. Still **no HTTP handlers**. | | Behaviour mixins | `rest_framework.mixins` | Five **action methods**: `list()`, `create()`, `retrieve()`, `update()`/`partial_update()`, `destroy()`. | | Concrete generics | `rest_framework.generics` | Ready classes such as `ListCreateAPIView` that bind HTTP handlers to those actions. | ## What GenericAPIView adds `GenericAPIView` subclasses `APIView` and contributes attributes and helper methods that every model-backed endpoint needs: - **`queryset`** and **`get_queryset()`** — the base set of objects. The method is what the mixins call; the attribute is its default. - **`serializer_class`**, **`get_serializer_class()`** and **`get_serializer()`** — which serializer to use, and a factory that builds it with a context holding `request`, `view` and `format`. - **`lookup_field`** (default `'pk'`) and **`lookup_url_kwarg`** — which model field and which URL keyword identify one object. - **`get_object()`** — fetches one object from the queryset, returns 404 when it is missing, and runs object-level permission checks. - **`filter_backends`** / **`filter_queryset()`** and **`pagination_class`** / **`paginate_queryset()`** — hooks for the filtering and pagination policies configured globally or per view. A bare `GenericAPIView` subclass serves no data: without `get()`, `post()` and friends, a GET, POST, PUT, PATCH or DELETE gets **405 Method Not Allowed** (only `OPTIONS`, which `APIView` itself implements, still answers). ## The five mixins Each mixin implements one **action** in terms of the helpers above, so it works for any model. | Mixin | Action method | Success response | Save/delete hook | |---|---|---|---| | `ListModelMixin` | `list()` | 200, paginated if a paginator is set | — | | `CreateModelMixin` | `create()` | 201, plus a `Location` header when the data carries a `url` key | `perform_create(serializer)` | | `RetrieveModelMixin` | `retrieve()` | 200, or 404 from `get_object()` | — | | `UpdateModelMixin` | `update()`, `partial_update()` | 200 | `perform_update(serializer)` | | `DestroyModelMixin` | `destroy()` | 204 with no body | `perform_destroy(instance)` | The mixins deliberately define **actions, not handlers**. `create()` does not know it will be called from `post()`. That separation lets the same mixins serve both the concrete generic views and DRF's viewsets, where a router rather than a method name decides which action runs. ## Concrete generics: the binding layer A concrete generic is a few lines: a mixin list, `GenericAPIView`, and one-line handlers. `ListCreateAPIView`, for example, is `ListModelMixin + CreateModelMixin + GenericAPIView` with `get()` returning `self.list(...)` and `post()` returning `self.create(...)`. The full set: 1. `ListAPIView` — `get()` to `list()`. 2. `CreateAPIView` — `post()` to `create()`. 3. `RetrieveAPIView` — `get()` to `retrieve()`. 4. `UpdateAPIView` — `put()` to `update()`, `patch()` to `partial_update()`. 5. `DestroyAPIView` — `delete()` to `destroy()`. 6. `ListCreateAPIView`, `RetrieveUpdateAPIView`, `RetrieveDestroyAPIView` and `RetrieveUpdateDestroyAPIView` — the common pairings. Collection endpoints (`/tickets/`) typically use `ListCreateAPIView`; item endpoints (`/tickets/<pk>/`) use `RetrieveUpdateDestroyAPIView`. Two classes and two URL patterns cover a full CRUD resource. ## One request traced end to end Following a `POST /tickets/` through a `ListCreateAPIView` shows how the layers cooperate: 1. Django resolves the URL and calls the view function that `as_view()` produced. 2. `APIView.dispatch()` wraps the request in DRF's `Request`, then `initial()` runs authentication, permission and throttle checks. 3. Dispatch finds the concrete class's `post()` handler, which simply returns `self.create(request, *args, **kwargs)`. 4. `CreateModelMixin.create()` asks `GenericAPIView` for a serializer through `get_serializer()`, validates it, calls `perform_create()`, and returns a 201 `Response`. 5. Any exception raised along the way — a validation error, a permission denial — is turned into an error response by the view's exception handler. Every layer contributes one slice, and none of them needs to know about the others' internals. ## Choosing the layer - Use a **concrete generic** when the endpoint is plain list/create/retrieve/update/delete over one model; customise through hooks (`get_queryset()`, `get_serializer_class()`, `perform_create()`). - Compose **mixins with `GenericAPIView`** yourself when you need an unusual pairing, and write the handler lines explicitly. - Drop to **`APIView`** when the endpoint is not model CRUD at all — a login exchange, a report, a bulk command. Forcing such an endpoint into generics usually means overriding so much that the generic machinery only obscures the code. The practical interview point is that almost all customisation of a generic view happens by overriding a small **hook** rather than the action method, because the action method carries validation, status codes and headers you would otherwise have to copy.

  • Why do DRF's mixins define list() and create() instead of get() and post()?
    Separating the action from the HTTP method lets the same mixin be bound in different ways. Concrete generics bind `get()` to `list()`, while DRF's viewsets let a router map methods to actions, so `ModelViewSet` reuses exactly the same five mixins. If the mixins defined `get()` directly, a view could not host both `list()` and `retrieve()` behind different routes.
  • What happens when a client sends DELETE to a DRF ListCreateAPIView endpoint?
    The view has no `delete()` handler, so `APIView.dispatch()` falls back to `http_method_not_allowed()` and the client gets 405 Method Not Allowed. Deleting single tickets belongs on an item endpoint, typically a `RetrieveUpdateDestroyAPIView` routed at `/tickets/<pk>/`.

saying these in an interview costs you the question

  • A bare GenericAPIView subclass answers GET and POST automatically.
  • The DRF mixins define get() and post() handlers directly.
  • CreateModelMixin returns 200 OK after saving a new object.
  • ListCreateAPIView also deletes objects when sent a DELETE request.
  • Pagination and filtering only work on viewsets, not on generic views.