skip to content

In a Django class-based view, what is View.setup() for, and when would you override it instead of __init__() or dispatch()?

level: middleimportance: nice to knowfreq 28%

answer

  1. runs before dispatch
  2. attaches request, args, kwargs
  3. __init__ never sees the request
  4. forget super() and it fails
  5. access mixins run later

basics

~10 s

View.setup() runs on each new view instance before dispatch() and stores request, args and kwargs on self. Override it, calling super(), to derive per-request attributes every handler needs; init() never sees the request.

solid answer

~40 s

The function returned by `as_view()` calls `cls(**initkwargs)`, then `setup(request, *args, **kwargs)`, then `dispatch()`. `__init__()` receives only initkwargs, so it cannot read the request; `setup()` is the first hook that can. The base `setup()` sets `self.request`, `self.args` and `self.kwargs` and aliases `head` to `get`; override it to derive attributes shared by every handler, such as a value parsed from a URL kwarg, and always call `super().setup(...)` first — if `request` is missing afterwards Django raises `AttributeError` asking whether you forgot `super()`. Prefer `dispatch()` for anything that should run after access checks, because mixins such as `LoginRequiredMixin` enforce access in `dispatch()`, and `setup()` runs before them. In tests, `view = MyView(); view.setup(request)` lets you call a single method directly.

code

python · 11 lines
python
from django.test import RequestFactory, SimpleTestCase

from .views import ExportView


class ExportViewSetupTests(SimpleTestCase):
    def test_format_is_normalised(self):
        request = RequestFactory().get("/export/CSV/")
        view = ExportView()
        view.setup(request, fmt="CSV")
        self.assertEqual(view.export_format, "csv")

go deeper

for a junior

Recall that setup() is where request, args and kwargs get attached to the view instance, and that overrides must call super().

for a middle

Explain the order constructor, setup(), dispatch(), handler, and why the request is unavailable in init().

for a senior

Point out that setup() runs before access mixins enforce anything in dispatch(), and keep permission-sensitive or expensive work out of it.

for a principal

Set a team convention for which hook owns what, so reviewers can spot work that runs before access checks at a glance.

## Where setup() sits in the lifecycle When a request reaches a Django class-based view, the function returned by `View.as_view()` runs four steps in order: 1. `self = cls(**initkwargs)` — the **constructor**, fed only the keyword arguments given to `as_view()` in the URLconf. 2. `self.setup(request, *args, **kwargs)` — the **per-request initialiser**. 3. A check that `self.request` now exists. 4. `self.dispatch(request, *args, **kwargs)` — the **router** to `get()`, `post()` and the other handlers. `setup()` exists so that there is one clearly named place, before any handler or `dispatch()` override, where the request is attached to the instance. ## What the base setup() does The default implementation is short: - If the class defines `get` but not `head`, it sets `self.head = self.get`, which is why `HEAD` requests work on read views. - It stores `self.request = request`. - It stores `self.args` and `self.kwargs`, the positional and keyword arguments captured from the URL pattern. After it returns, every method on the view can read `self.request`, `self.kwargs["slug"]` and so on without having them passed in. ## Choosing the right hook | Hook | Sees the request? | Runs relative to access mixins | Good for | |---|---|---|---| | `__init__(**kwargs)` | No | Before | Rarely anything; initkwargs are already applied by the base constructor | | `setup(request, ...)` | Yes | **Before** `LoginRequiredMixin`, `PermissionRequiredMixin` and `UserPassesTestMixin` | Cheap derived attributes every handler and mixin may read | | `dispatch(request, ...)` | Yes | Mixins' `dispatch()` runs around yours depending on base-class order | Cross-cutting logic around every verb | | `get()` / `post()` | Yes | After the access check has passed | Verb-specific work | The ordering column is the one that bites. The access mixins in `django.contrib.auth.mixins` enforce their checks by overriding `dispatch()`. Since `setup()` runs **before** `dispatch()`, anything in `setup()` executes for anonymous and unauthorised users too. So: - **Do** parse and normalise values from `self.kwargs` or the query string in `setup()`. - **Do not** run expensive queries, touch per-user data, or assume `request.user` is authenticated in `setup()`; move that to `dispatch()` after `super()`, or into the handler. ## Overriding it correctly ```python from django.views import View class ExportView(View): def setup(self, request, *args, **kwargs): super().setup(request, *args, **kwargs) self.export_format = kwargs.get("fmt", "csv").lower() ``` Call `super().setup(...)` **first**, so `self.request`, `self.kwargs` and the `head` alias exist before your code uses them. If you forget `super()` entirely, the `as_view()` function notices that the instance has no `request` attribute and raises `AttributeError` with the message "Did you override setup() and forget to call super()?" — a deliberate guard, because the failure would otherwise surface later as a confusing error deep inside a mixin. ## Why not __init__()? The base `View.__init__()` just assigns each initkwarg onto the instance. Overriding it buys nothing that `setup()` does not, and has two costs: the request is not available yet, and a signature that forgets `**kwargs` breaks every `as_view(...)` override for that class. There is almost never a reason to touch `__init__()` on a Django view. ## setup() in tests Because `setup()` is separate from `dispatch()`, a unit test can build a view, attach a request and call one method without going through the URL resolver or middleware: - Build a request with `RequestFactory().get("/")`. - Instantiate the view with `MyView()` and call `view.setup(request)`. - Call the method under test, for example `view.get_context_data()`, and assert on the result. This is the pattern Django's own testing documentation shows, and it keeps tests of context building independent of rendering. ## setup() versus dispatch() overrides Both hooks see the request, so the choice comes down to timing and intent: - `setup()` is for **state**: attributes that later code reads, computed without side effects. - `dispatch()` is for **control flow**: code that may short-circuit with its own response (a redirect, a 403) or wrap every handler, which is why the access mixins live there. - A `dispatch()` override that needs a value computed in `setup()` can rely on it, because `setup()` has always run first. ## Common mistakes - Overriding `setup()` without `super()`. - Loading user-specific data in `setup()` and leaking it to anonymous callers before `LoginRequiredMixin` rejects them. - Doing work in `__init__()` that needs `self.request`.

  • Your setup() override loads the current user's workspace, and anonymous visitors now trigger errors. Why?
    `setup()` runs before `dispatch()`, and `LoginRequiredMixin` enforces authentication in `dispatch()`. So the workspace lookup runs for anonymous users before the mixin can redirect them, hitting `AnonymousUser`. Move user-dependent work into the handler or into a `dispatch()` override that calls it after `super()`, and keep `setup()` for request-independent derivations.
  • What error do you get if a setup() override forgets to call super(), and where is it raised?
    The function returned by `as_view()` checks `hasattr(self, "request")` right after `setup()`. Without `super()`, the attribute is never set, so it raises `AttributeError` with a message asking whether you overrode `setup()` and forgot to call `super()`. It fires on the first request to that view, before `dispatch()` runs.

saying these in an interview costs you the question

  • Overriding __init__() to read self.request
  • Believing setup() runs after LoginRequiredMixin has checked the user
  • Calling super().setup() last, after code that reads self.kwargs
  • Thinking setup() runs once per URL pattern rather than once per request
  • Saying a missing super() in setup() is silently harmless