In Django REST Framework, in what order does APIView.initial() run content negotiation, authentication, permissions and throttles, and why does that order matter?
answer
- format first, then identity
- who, then may they, then how often
- the verb is checked last
- objects are not checked here
basics
~20 sAPIView.initial() negotiates the renderer, determines the API version, authenticates, checks permissions, then checks throttles; only afterwards is the handler looked up. So errors render in the negotiated format, bad credentials fail even on AllowAny views, and permission-rejected requests never reach throttles.
solid answer
~40 s`dispatch()` wraps the request and calls `initial()`, which runs in this order: format suffix, **content negotiation** (setting `request.accepted_renderer`), **versioning**, **authentication** (`perform_authentication()` just touches `request.user`), **permissions** (`has_permission()` for each class) and **throttles**. Only then is the method handler picked, so a disallowed verb gets 405 only after those checks pass. The order has consequences: negotiation first lets every later error render in the client's format; authentication runs eagerly, so an invalid token fails even on an `AllowAny` view; throttles run last, so requests a permission rejects are never counted; and a protected view answers an unauthenticated wrong-verb request with 401 or 403 rather than 405. Object-level permissions are not part of `initial()` at all: generic views check them in `get_object()`.
code
python · 16 linesfrom rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView
class ConvertView(APIView):
permission_classes = [IsAuthenticated]
throttle_classes = [UserRateThrottle] # needs DEFAULT_THROTTLE_RATES['user']
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs) # negotiation, auth, perms, throttles
self.rates = load_rates_for(request.user) # runs only if all checks passed
def get(self, request):
return Response(convert_with(self.rates, request.query_params))go deeper
Recall the order inside initial(): negotiation, versioning, authentication, permissions, throttles, and that the handler runs only after all of them.
Explain what each step sets or raises, why perform_authentication() is just request.user, and where object permissions are actually checked.
Use the order to reason about incidents: invalid tokens failing public endpoints, rejected traffic escaping throttles, and 401/403 where a client expected 405.
Decide which limits belong in DRF and which at an earlier layer, given that DRF throttles only count requests that already passed authentication and permissions.
## The request lifecycle in `APIView` Every Django REST Framework (DRF) view — `APIView`, `@api_view` functions, generic views and viewsets — goes through `APIView.dispatch()`: 1. `initialize_request()` wraps Django's `HttpRequest` in a DRF `Request`, giving it the view's parsers, authenticators and content negotiator. 2. `initial()` runs the pre-handler checks (below). 3. The handler for the HTTP method is looked up; a method not in `http_method_names` or not defined maps to `http_method_not_allowed()`, which raises `MethodNotAllowed` (405). 4. The handler runs and returns a response. 5. Any exception from steps 2-4 goes to `handle_exception()` and the exception handler. 6. `finalize_response()` attaches the renderer and headers. ## What `initial()` does, in order | Step | Method | Sets or raises | |---|---|---| | 1 | `get_format_suffix()` | `self.format_kwarg` from a `.json`-style suffix | | 2 | `perform_content_negotiation()` | `request.accepted_renderer`, `request.accepted_media_type`; `NotAcceptable` (406) | | 3 | `determine_version()` | `request.version`, `request.versioning_scheme` | | 4 | `perform_authentication()` | evaluates `request.user`; may raise `AuthenticationFailed` | | 5 | `check_permissions()` | calls `has_permission()` per class; raises `NotAuthenticated` or `PermissionDenied` | | 6 | `check_throttles()` | calls `allow_request()` per class; raises `Throttled` (429) | `perform_authentication()` is a single line, `request.user`: reading that property runs each authenticator in turn until one returns a user. Overriding it with `pass` makes authentication lazy — performed only when code first reads `request.user` or `request.auth`. ## Why the order matters - **Errors render in the right format.** Negotiation comes first, so a 401 or 429 raised later is rendered as JSON for a JSON client. If negotiation itself fails, `finalize_response()` falls back to the first renderer. - **Bad credentials fail everywhere.** Authentication runs before permissions and regardless of them. A client sending an invalid token to an `AllowAny` endpoint gets an authentication error, even though an anonymous request would have succeeded. - **Rejected requests are not throttled.** Throttles run after permissions, so a request refused with 401 or 403 never reaches `allow_request()` and is not recorded in the throttle history. Rate limiting unauthenticated traffic on a protected endpoint therefore needs the limit somewhere earlier, or an endpoint where anonymous requests pass permissions. - **405 comes last.** The handler lookup happens after `initial()`. With `IsAuthenticated`, an anonymous `DELETE` to a GET-only endpoint gets 401 or 403; with `AllowAny`, the same request gets 405. - **Object permissions are elsewhere.** `initial()` only calls `has_permission()`. `has_object_permission()` runs when generic views call `get_object()`, which calls `check_object_permissions()`; a list endpoint or a custom `APIView` that never calls it gets no object checks. ## Where you can hook in - Override `initial()` (calling `super()`) to add a per-request step after the standard checks, such as loading the tenant. - Override `check_permissions()` or `permission_denied()` to customise failures. - Set `authentication_classes`, `permission_classes` and `throttle_classes` per view, or with the matching decorators under `@api_view`. ## Defaults that change the picture - `DEFAULT_PERMISSION_CLASSES` defaults to `AllowAny`, so step 5 passes everything unless a project or view sets a stricter class. - `DEFAULT_THROTTLE_CLASSES` defaults to an empty list, so step 6 does nothing until throttles are configured. - `DEFAULT_AUTHENTICATION_CLASSES` defaults to session then basic authentication; step 4 runs both in turn for an anonymous request and settles on `AnonymousUser`. - A view attribute such as `permission_classes = [...]` **replaces** the default list; it does not add to it. ## A trace for the currency endpoint A `GET /convert/?from=EUR&to=USD&amount=100` with `Accept: application/json` and a bearer token: negotiation picks `JSONRenderer`; no versioning is configured; the token authenticator validates the header and sets `request.user`; `IsAuthenticated` passes; the user-rate throttle records the request; `get()` runs. Had the token been expired, step 4 would raise and steps 5-6 would never run.
- Why can a client get an authentication error from an endpoint marked AllowAny?`initial()` authenticates before checking permissions. If the request carries credentials that an authenticator rejects, such as an expired token, that authenticator raises `AuthenticationFailed` while `request.user` is evaluated, and the permission check never runs. A request with no credentials at all passes as anonymous.
- Does a throttle count requests that a permission class rejected?No. `check_throttles()` runs after `check_permissions()`, and a rejection raises before any throttle's `allow_request()` is called, so nothing is recorded. Rate limits in DRF therefore apply to requests that already passed authentication and permissions.
An airport: the desk first agrees which language to talk to you in (negotiation), passport control checks who you are (authentication), the gate checks your boarding pass (permission), and only then does the gate's headcount limit apply (throttle). Someone turned away at passport control never counts toward the gate's headcount.
saying these in an interview costs you the question
- DRF checks throttles before authentication so abusive clients are stopped early.
- An AllowAny view ignores the credentials a client sends.
- initial() also runs has_object_permission() for the requested object.
- A wrong HTTP method is rejected with 405 before any authentication runs.
- Content negotiation happens after the handler returns.