What does Django's cache_page decorator do, and what do its timeout, cache and key_prefix arguments control?
answer
- whole response, not a value
- seconds as the first argument
- which alias, which namespace
- one entry per full URL
basics
~20 sDjango's cache_page stores a view's whole GET or HEAD response in the cache and serves it for later requests to the same URL. timeout is the lifetime in seconds, cache picks the CACHES alias, and key_prefix namespaces the keys.
solid answer
~40 s`django.views.decorators.cache.cache_page(timeout, *, cache=None, key_prefix=None)` wraps a view in Django's `CacheMiddleware`. On a GET or HEAD request it looks up a stored response for the full URL, query string included, and returns it without calling the view; on a miss it calls the view and, if the response is cacheable (status 200, not streaming, not marked `private`, `no-cache` or `no-store`), stores it. `timeout` is in seconds and takes precedence over a `max-age` the view set; `cache` names an alias from `CACHES` (default `"default"`); `key_prefix` works like `CACHE_MIDDLEWARE_KEY_PREFIX` for this view and is combined with the alias's `KEY_PREFIX`. It also sets `Expires` and `Cache-Control: max-age` headers on the response. `cache` and `key_prefix` are keyword-only.
code
python · 14 lines# events/urls.py
from django.urls import path
from django.views.decorators.cache import cache_page
from . import views
urlpatterns = [
path(
"calendar/",
cache_page(60 * 15, cache="pages", key_prefix="calendar")(
views.public_calendar
),
),
]go deeper
Recall the decorator, that timeout is in seconds, and that it caches the whole response per URL for GET and HEAD.
Explain the cache and key_prefix arguments, the cacheability conditions, and the Expires and max-age headers it adds.
Choose where to apply it (view, URLconf, dispatch) and recognise pages that must not be cached because they vary per user.
Decide which pages are safe for whole-response caching at all, and how staleness bounded only by timeout fits the product.
## What cache_page is `cache_page` lives in `django.views.decorators.cache`. It is Django's **per-view cache**: instead of caching a value you compute, it caches the **entire `HttpResponse`** a view returns, so the next matching request is answered without running the view, its queries or its template. Its signature in current Django is: ```python cache_page(timeout, *, cache=None, key_prefix=None) ``` Under the hood it is built from `CacheMiddleware` (the single-class combination of `FetchFromCacheMiddleware` and `UpdateCacheMiddleware`) turned into a decorator, so it follows the same rules as the site-wide cache middleware. ## The three arguments | Argument | Meaning | Default | |---|---|---| | `timeout` | Seconds to keep the stored response | required | | `cache` | Alias in `CACHES` to store it in | `"default"` | | `key_prefix` | Extra namespace for this view's keys, like `CACHE_MIDDLEWARE_KEY_PREFIX` | `""` | - **`timeout`** is positional and required. `@cache_page(60 * 15)` keeps the page for 15 minutes. For storage in Django's cache, this value **takes precedence** over any `max-age` the view put in its `Cache-Control` header. - **`cache`** lets a heavy page live in a different store, for example `@cache_page(900, cache="pages")`. - **`key_prefix`** separates views or sites that would otherwise build the same key. The docs point out it is **concatenated** with the alias's `KEY_PREFIX` from `CACHES`, not a replacement for it. ## What gets cached and when A request passes through two steps: 1. **Before the view**: for GET and HEAD only, Django builds a key from the full absolute URL (scheme, host, path and query string) plus any headers the page is known to vary on, and looks it up. A hit returns the stored response immediately. 2. **After the view**: on a miss, the view runs. The response is stored only when it is safe to reuse: - status code `200`, - not a streaming response, - `Cache-Control` does not contain `private`, `no-cache` or `no-store`, - `Vary` is not `*`, - it does not set a cookie while varying on `Cookie`. `/events/?month=10` and `/events/?month=11` are therefore separate entries, and a POST is never served from the cache. ## Headers it adds When it stores a response, `cache_page` also calls `patch_response_headers()`, which adds an `Expires` header (if none is set) and a `max-age` in `Cache-Control`. Browsers and shared caches downstream see those headers too, which is useful for a public page and dangerous for a private one. ## Where to apply it - **On the function**: `@cache_page(900)` above `def calendar(request): ...`. - **In the URLconf**: `path("events/", cache_page(900)(views.calendar))`, which keeps the view reusable without caching. The docs recommend this when the same view may be used on a site that should not cache it. - **On a class-based view**: wrap the result of `as_view()` in the URLconf, or use `method_decorator(cache_page(900), name="dispatch")` on the class. ## Checking that it works - **Look at the headers**: a response from a `cache_page` view carries `Expires` and `Cache-Control: max-age=...`. A hit served from the cache also carries an `Age` header estimating how long ago it was stored. - **Count queries**: with database query logging enabled, the second request to the same URL should run no view queries at all. - **Remember the alias**: in development the default alias is often `LocMemCache` or `DummyCache`. With `DummyCache` nothing is ever stored, so a decorated view looks uncached; with `LocMemCache` each worker process keeps its own copy. - **Test with the query string you really use**: `/calendar/` and `/calendar/?page=1` are different keys, so a paginator that always adds `?page=1` halves the hit rate for the first page. ## Limits to remember - It does **not** vary by user on its own. Because the decorator wraps the view directly, it stores the response **before** response middleware, such as the session middleware, has added `Vary: Cookie`. Pages that differ per user need a separate strategy. - Invalidating one cached page early is awkward, because the key is a hash of the URL and headers; most teams rely on the timeout. - For a site-wide cache, use the middleware pair instead of decorating every view.
- A view decorated with cache_page(900) sets Cache-Control max-age=60 itself. How long does Django keep the page?900 seconds in Django's cache: the decorator's timeout takes precedence over the view's `max-age` for storage. The header seen by browsers is patched with the smaller of the two ages, so downstream caches are told 60 seconds while Django's own copy lives for 15 minutes.
- Does cache_page cache a POST to the same URL?No. The fetch step only looks up GET and HEAD requests and marks every other method as not to be stored, so a POST always reaches the view. A HEAD request can be answered from the stored GET response, because the middleware assumes both carry the same headers.
saying these in an interview costs you the question
- cache_page caches each view's response once, regardless of the query string.
- The timeout argument of cache_page is in minutes.
- cache_page automatically keeps logged-in users' pages separate.
- cache_page also caches POST responses to the same URL.
- key_prefix replaces the KEY_PREFIX configured in CACHES.