Django REST Framework
Django REST Framework layers serializers, viewsets with routers, and pluggable authentication, permission and throttle classes over Django. Interviewers probe validation and permissions.
on this pageshowhide
guide
overview
~1 minDjango REST Framework (DRF) is the usual way to build a JSON API on Django. It adds a request pipeline to Django's views: the body is parsed, the caller identified, the request checked against permission and rate policies, handled, and rendered back in a negotiated format. Almost every class in that pipeline is a swappable default, and many of the bugs interviewers ask about come from a default nobody questioned — an endpoint open to anonymous callers, a list that returns other users' rows, a nested payload that runs a query per item. The hub follows that pipeline. [Serializers](/topics/be-drf-serializers) hold the data shape and the validation rules, nested relations and their query cost included. [API views and routers](/topics/be-drf-views) run from `APIView` through generic classes and mixins up to ViewSets that routers turn into URLs. [Auth and throttle policies](/topics/be-drf-access) decide who the caller is, what they may touch and how often. [The listing and wire contract](/topics/be-drf-contract) covers pagination, filtering, formats, versioning and error bodies — what a client actually sees. [Tests and schemas](/topics/be-drf-quality) asks how you prove the API behaves and describe it to callers. Junior rounds check that you can name the right hook: where a validation rule lives, which mixin supplies which action, what an unconfigured view allows. Senior rounds become debugging stories — a slow list, a 403 where a 401 was expected, a rate limit that leaks under load — where the answer is knowing which layer ran and which never did. Start with the request and response objects, then serializers and the generic views; permissions make sense only once you know where a single object is fetched.
primer
### A serializer works in both directions A serializer is an output formatter and an input gate at once: it turns objects into primitive data for the response, and turns untrusted request data into a checked dict before anything is saved. One class doing both is convenient until the read and write shapes drift apart. `ModelSerializer` derives fields and save logic from a model; a plain `Serializer` makes you spell them out. Interviewers probe where each rule belongs and what happens to it on a partial update. ### The view ladder trades explicitness for less code `APIView` gives method handlers plus DRF's policies. `GenericAPIView` adds a queryset, a serializer class and lookup helpers; mixins add the CRUD actions; ViewSets group actions so a router can generate the URLs. Each rung removes code and hides more of the flow. A senior answer says when that indirection pays — uniform, model-backed resources — and when an explicit `APIView` reads better. ### Override a hook, keep the plumbing Generic views are assembled from small methods: `get_queryset`, `get_serializer_class`, `get_object`, `perform_create`, `get_permissions`. The idiomatic change overrides one and leaves status codes, validation and pagination alone; rewriting a whole action, or fetching a row by hand, tends to drop scoping, filtering or an object check. ### Policies run before your code Authentication, permission and throttle classes run ahead of the handler on every request, set globally in the `REST_FRAMEWORK` settings and overridable per view. Two facts drive a lot of questions: an unconfigured project lets anonymous callers in, and a view's own policy list replaces the global one instead of extending it. The fixed order of the checks explains several surprising status codes. ### Object checks depend on the call site Row-level rules live in `has_object_permission`, which only fires when a view goes through `get_object()`. Collection endpoints never do, so for lists the real authorization boundary is the queryset itself. When an interviewer describes a data leak, they usually want to hear the queryset scoped to the caller. ### The queryset owns performance Serializers read attributes; they do not plan queries. Any nested or computed field that touches a relation costs a query per row unless the view loaded it up front, and DRF will not do that for you. A slow list endpoint is therefore a view problem, diagnosed by counting queries rather than by reading the serializer. ### The wire contract is configuration Pagination, filter backends, parsers, renderers, versioning and the exception handler are all pluggable classes, and together they decide what a client can send and what comes back, errors included. Several of them do nothing, or permit everything, until configured — which is why "what is the default here" is such a common opening question.
- Serializer
- A class that converts objects to primitive data for responses and validates incoming data into a dict that can be saved.
- ModelSerializer
- A serializer that generates its fields, validators and save methods from a Django model named in its inner Meta class.
- APIView
- DRF's base class-based view. It swaps in DRF's request and response objects and runs authentication, permission and throttle policies before the handler.
- Generic view
- A view built on GenericAPIView, which adds a queryset, a serializer class and lookup helpers that mixins use to implement CRUD actions.
- ViewSet
- A class that groups related actions such as list and retrieve under one resource, leaving the HTTP-method-to-action mapping to a router.
- Router
- An object that inspects registered ViewSets and generates their URL patterns and route names, including extra actions.
- Action
- A named ViewSet operation — built-in like list or create, or custom via the @action decorator — exposed on self.action during a request.
- Authentication class
- A pluggable class that identifies the caller from the request and sets request.user and request.auth, or leaves the request anonymous.
- Permission class
- A pluggable class that allows or denies a request at view level, and optionally for one specific object.
- Object-level permission
- A check against a single model instance, run only when a view fetches that instance through get_object().
- Throttle class
- A pluggable class that limits request rate per user, IP or named scope, answering 429 once a client exceeds its rate.
- Filter backend
- A class that narrows a generic view's queryset from request parameters, such as field filters, search or ordering.
- Pagination class
- A class that splits a list response into pages and adds navigation data; styles include page number, limit-offset and cursor.
- Content negotiation
- The step that chooses a renderer — JSON, the HTML browsable API — by matching the client's Accept header against the view's renderers.
- Exception handler
- The function that turns exceptions raised in a DRF view into error responses; replaceable in settings to change the error body shape.
Follow one request through a ViewSet. The router generated the URL when the ViewSet was registered, and dispatch lands on the matching action. Before that action runs, DRF wraps Django's request in its own `Request` and works through the view's policies: it picks a renderer, reads the API version if a versioning class is set, identifies the caller, checks view-level permissions, then applies throttles. What happens next depends on the action: - **A list** takes `get_queryset()`, narrows it through the filter backends, cuts a page and serializes that page. - **A detail action** calls `get_object()`, which applies the same queryset and filters, looks the row up from the URL, and only then runs the object-level permission check. - **A write** builds a serializer from `request.data`, validates it and calls `save()`; the `perform_*` hooks are where server-side values join the validated data. Whatever comes out — data or a raised exception — is rendered in the negotiated format, and exceptions pass through the exception handler first. The hub's sections sit along this path: views and routers are the frame, access policies guard the entrance, serializers stand in the middle of every read and write, the contract classes are plugged in around the edges, and tests and schemas check the whole path from the outside. ```python class DocumentViewSet(viewsets.ModelViewSet): serializer_class = DocumentSerializer permission_classes = [IsAuthenticated, IsOwner] # IsOwner: a custom object check def get_queryset(self): return (Document.objects.filter(owner=self.request.user) .select_related("folder").prefetch_related("tags")) def perform_create(self, serializer): serializer.save(owner=self.request.user) router = DefaultRouter() router.register("documents", DocumentViewSet, basename="document") ``` Four sections meet in those lines. The scoped queryset is what keeps the list private, since `IsOwner` guards only single-object actions; preloading the folder and tags keeps the nested serializer from querying per row; the owner comes from the authenticated user rather than the payload; and because the class declares no `queryset` attribute, the router cannot infer a basename and has to be given one.
- Request & Reply Objects →
APIView, the Request and Response objects and the policy order: the base every other section builds on.
- Serializers →
Serializers carry the data shape and the validation rules; every view, test and performance question assumes you can read one.
- Generic Classes & Mixins →
Generic views and mixins hold get_queryset, get_object and perform_create, the hooks the rest of the hub overrides.
- ViewSets & Actions →
ViewSets, custom actions and routers, and when that extra indirection earns its place.
- Permission Classes →
Default-open views, view versus object checks, and why lists leak: the authorization questions interviewers press hardest.
- Listing & Wire Contract →
Pagination, filtering, formats and error bodies: what clients see, and where the defaults allow too much.
Assuming a view is private because the project has login: the default permission is
AllowAny, and a view'spermission_classesreplaces the global list.Relying on
has_object_permissionto protect a list endpoint; it fires only throughget_object(), so collections must be scoped inget_queryset().Taking an owner or author field from the request body instead of setting it from the authenticated user when the object is saved.
Blaming the serializer for per-row queries the view caused; preloading belongs on the queryset, and a method field that filters a relation skips the prefetch.
Writing cross-field rules in
validate()that assume every field is present, then shipping a PATCH endpoint where half of them are missing.Presenting DRF throttling as an abuse control; it is best-effort rate shaping that the cache backend and proxy setup can weaken — see Rate Limit Classes.
Testing every endpoint with
force_authenticate, so the real authentication classes and their CSRF check never run before a browser client hits them.Exposing a large table with no maximum page size or no explicit ordering whitelist, letting clients request huge pages or sort by unindexed columns.
This guide assumes Django REST Framework 3.18, the release the answers below are written against. The core API — serializers, generic views, routers, policy classes — has been stable for years, but a few answers hinge on recent changes: - **Schema generation.** CoreAPI-based schemas were removed in 3.17, and the remaining built-in OpenAPI generator is documented as deprecated, with external generators recommended instead. "How do we produce an OpenAPI document" is now a question about the tooling in [OpenAPI Generation](/topics/be-drf-quality-openapi), not about DRF's own classes. - **Router registration.** Since 3.15, registering two ViewSets under the same basename on one router fails at startup instead of silently producing clashing route names. The defaults that cause the most trouble — no permission checks, no throttling and no pagination until configured — have not changed, so questions about them read the same on any recent release. When an answer depends on a release, name it.
DRF sits on Django and its ORM, so its strengths and costs are Django's: models, migrations, the admin and the auth system come along, and so does the synchronous, model-first way of building things. Around it, a typical project adds django-filter for field filtering, a token or JWT library when built-in tokens are too simple, a third-party OpenAPI generator such as drf-spectacular, and a CORS package when browser clients live on another origin. Interviewers also expect you to place DRF against its alternatives. Django Ninja keeps Django but declares request and response shapes with type hints and Pydantic models. FastAPI is a separate, async-first framework built around the same type-hint idea. The trade-off that separates them: DRF's class hierarchy and serializers give a lot for free on model-backed CRUD, at the cost of indirection, while the type-hint frameworks make the request and response contract explicit in the function signature.
explore
- Serializers19 questions
- Plain vs Model-Backed5 questions
- Input Validation Hooks5 questions
- Nested & Writable Relations5 questions
- Nested Payload Cost4 questions
- API Views & Routers13 questions
- Request & Reply Objects4 questions
- Generic Classes & Mixins4 questions
- ViewSets & Actions5 questions
- Auth & Throttle Policies13 questions
- Authenticator Classes4 questions
- Permission Classes5 questions
- Rate Limit Classes4 questions
- Listing & Wire Contract17 questions
- Pagination Styles4 questions
- Filtering, Search & Ordering5 questions
- Parsers, Renderers & Versioning4 questions
- Exception Responses4 questions
- Tests & Schemas9 questions
- Client-Level Checks5 questions
- OpenAPI Generation4 questions
questions
71 · 5 sectionsIn Django REST Framework, what is the difference between Serializer and ModelSerializer, and when would you choose a plain Serializer?
basics
~20 sA plain Serializer declares every field and needs its own create() and update(); a ModelSerializer builds fields and validators from a model through Meta and implements both methods. Choose a plain Serializer when the payload is not one model.
In Django REST Framework, what does serializer.is_valid() do, and what changes when you call it with raise_exception=True?
basics
~20 sis_valid() runs the whole validation pipeline on the data passed as data=, stores either validated_data or errors, and returns a boolean. With raise_exception=True an invalid payload raises serializers.ValidationError, which DRF's exception handler turns into a 400 response carrying the errors.
In a Django REST Framework ModelSerializer, how do Meta.fields, read_only_fields and extra_kwargs decide which model fields are exposed and writable?
basics
~20 sMeta.fields (or exclude) chooses which model fields exist on the serializer; read_only_fields marks some as output-only; extra_kwargs passes any other field argument. Both shortcuts apply only to generated fields and are ignored for fields declared explicitly on the class.
In Django REST Framework, when do you use validate_<field_name>() versus validate(), and in what order does is_valid() run them?
basics
~20 svalidate_<field_name>(value) checks one field after its own conversion and validators; validate(attrs) checks rules across fields after every field passed and serializer-level validators ran. Both return the value, and a ValidationError in validate() lands under non_field_errors unless given a dict.
In Django REST Framework, what does GenericAPIView add on top of APIView, and how do the mixins combine into concrete views like ListCreateAPIView?
basics
~10 sGenericAPIView 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.
In Django REST Framework, what do APIView and the @api_view decorator add over a plain Django view, and how do they differ?
basics
~20 sBoth give a view DRF's Request and Response, content negotiation, authentication, permission and throttle checks, and API exception handling. APIView is a class with get()/post() methods and policy attributes; @api_view(['GET']) wraps a function into an APIView subclass, with policy decorators.
In Django REST Framework, how do request.data and request.query_params differ from Django's request.POST and request.GET?
basics
~10 srequest.query_params is DRF's name for Django's request.GET. request.data is the body parsed by the view's parsers from its Content-Type, for POST, PUT and PATCH alike, JSON included; Django's request.POST holds only form-encoded POST data.
In Django REST Framework, how does a ViewSet differ from an APIView, and what do ModelViewSet and ReadOnlyModelViewSet provide?
basics
~20 sA ViewSet defines actions such as list() and retrieve() instead of get() and post() handlers; as_view() or a router binds HTTP methods to them. ModelViewSet provides all six CRUD actions; ReadOnlyModelViewSet provides only list() and retrieve().
In a Django REST Framework ListCreateAPIView for support tickets, how do you set the requester from request.user, and why override perform_create()?
basics
~10 sOverride perform_create(self, serializer) and call serializer.save(requester=self.request.user). CreateModelMixin.create() still validates input, calls the hook, and returns 201, so only the save step changes and none of the response plumbing is copied.
In Django REST Framework, what do authentication classes do, and what do request.user and request.auth hold after TokenAuthentication succeeds?
basics
~20 sAuthentication classes identify the caller; permission classes decide access. DRF tries each class in order, and the first returning (user, auth) sets request.user and request.auth: for TokenAuthentication, the token's user and Token instance; otherwise AnonymousUser and None.
In Django REST Framework, which permission applies to a view when none is configured, and how do you require authentication across the whole API?
basics
~10 sDRF's default permission is AllowAny, so an unconfigured view accepts anonymous requests. Set DEFAULT_PERMISSION_CLASSES to IsAuthenticated in REST_FRAMEWORK; a view's own permission_classes then replaces that list rather than adding to it.
In Django REST Framework, how do you switch on AnonRateThrottle and UserRateThrottle, and what does a throttled client receive back?
basics
~20 sDRF throttles nothing by default: list the classes in DEFAULT_THROTTLE_CLASSES and give 'anon' and 'user' rates like '100/day' in DEFAULT_THROTTLE_RATES. A throttled request gets 429 with a Retry-After header and a 'Request was throttled.' detail.
In Django REST Framework, how do you run TokenAuthentication for a mobile app beside SessionAuthentication for the web app, and why is CSRF session-only?
basics
~10 sList TokenAuthentication and SessionAuthentication in DEFAULT_AUTHENTICATION_CLASSES and add rest_framework.authtoken. Only SessionAuthentication enforces CSRF, because browsers attach session cookies automatically, while a token in the Authorization header must be added by the client.
In Django REST Framework, how do has_permission and has_object_permission differ, and when does DRF call each of them?
basics
~10 shas_permission runs on every request before the handler and sees only the request and view; has_object_permission sees a model instance and runs only when check_object_permissions() is called, which generic views do inside get_object().
In Django REST Framework, what happens when a view raises NotFound, PermissionDenied or Throttled, and what body does the default exception handler return?
basics
~20 sDRF catches APIException subclasses raised in a view and returns a Response with the class's status_code and a body of {"detail": message}. Django's Http404 and PermissionDenied are mapped to 404 and 403; other exceptions propagate as a 500.
In Django REST Framework, how do filter_backends let clients filter, search and order a list endpoint such as GET /jobs/?
basics
~10 sA DRF generic view passes its queryset through each class in filter_backends: DjangoFilterBackend (django-filter) handles field filters like ?remote=true, SearchFilter handles ?search= over search_fields, and OrderingFilter handles ?ordering= limited to ordering_fields.
In Django REST Framework, how do you enable pagination for list endpoints, and what does PageNumberPagination put in the response?
basics
~10 sDRF paginates nothing by default: set DEFAULT_PAGINATION_CLASS and PAGE_SIZE, or pagination_class on a view. PageNumberPagination reads ?page=N and returns count, next, previous and results; a page beyond the end is a 404.
How would you write a custom EXCEPTION_HANDLER in Django REST Framework so a partner API returns one error envelope, and what will it still miss?
basics
~20 sWrite a function (exc, context) that calls rest_framework.views.exception_handler, reshapes response.data into your envelope, and set it as EXCEPTION_HANDLER. It never sees responses a view returns itself, errors outside DRF views, or unhandled exceptions it returns None for.
In Django REST Framework, why should a view using OrderingFilter declare ordering_fields explicitly, and what happens if you leave it unset or use '__all__'?
basics
~10 sOrderingFilter's ordering_fields is the whitelist for ?ordering=. Unset, it allows every readable serializer field; 'all' allows every concrete model field and annotation. An explicit list stops clients sorting by hidden or unindexed columns.
In Django REST Framework, how do you write an APITestCase for an authenticated create endpoint, and what should it assert?
basics
~10 sSubclass APITestCase, call self.client.force_authenticate(user=user), POST the payload with format='json', then assert status 201, the key fields of response.data, and that the database row exists with the right owner.
In Django REST Framework 3.18, what is the status of built-in OpenAPI schema generation, and what do new projects use instead?
basics
~10 sDRF 3.18 still ships SchemaGenerator, AutoSchema, generateschema and get_schema_view, but its documentation deprecates them in favour of third-party packages and recommends drf-spectacular for OpenAPI 3. CoreAPI schema support was already removed in 3.17.0.
In Django REST Framework, how does the browsable API differ from generated OpenAPI documentation, and why is it no substitute?
basics
~20 sThe browsable API is an HTML renderer that shows one endpoint at a time, live, to a person in a browser acting as the current user. Generated OpenAPI documentation is a machine-readable description of every operation that partners and tools can use without your server.
In Django REST Framework's APIClient, what does format='json' change, and what is sent without it?
basics
~10 sformat='json' makes APIClient render the body with JSONRenderer and send Content-Type application/json. Without it, DRF uses TEST_REQUEST_DEFAULT_FORMAT, which defaults to 'multipart', so nested dicts and None cannot be sent.
In Django REST Framework, when do you test a view with APIRequestFactory instead of APIClient, and what must you do by hand?
basics
~20 sAPIRequestFactory only builds an HttpRequest; you call the view directly, so there is no URL routing or middleware. Use it to unit-test a view class; you must map viewset actions, pass URL kwargs, authenticate with force_authenticate(request, ...) and render the response yourself.