1.Per-site, per-view, template fragment, and the low-level API
Django gives you four places to cache, and they differ in how much of the response they store. **Per-site** caches whole pages through two middleware, and only for `GET`/`HEAD` requests that returned 200. **Per-view** does the same for one view, via `@cache_page(seconds)`. **Template fragment** caches a block inside a template with `{% cache 500 sidebar %}`, so the surrounding page stays dynamic. **The low-level API** — `cache.get()`, `cache.set()`, `cache.get_or_set()` — caches any value you choose, which is the only one of the four that can cache something that is not part of a rendered response. Go as narrow as the problem allows: the wider the level, the more of the page becomes stale at once.
Key features
- Per-site caching needs `UpdateCacheMiddleware` **first** and `FetchFromCacheMiddleware` **last** in `MIDDLEWARE` — the order is not stylistic, it is how the request wraps the response
- Per-site and per-view caching only ever cache `GET` and `HEAD` requests that returned status 200; anything else passes straight through
- Query strings are part of the key, so `?page=2` and `?page=3` cache separately — and `CacheMiddleware` automatically varies on the `Authorization` header
- `@cache_page(60 * 15)` can be applied in `urls.py` instead of on the view, which keeps the view free of any knowledge that it is cached
- `{% cache 500 sidebar request.user.username %}` — everything after the fragment name is a `vary_on` value, and each distinct combination is a separate cache entry
- The low-level API is the only level that caches a *value* rather than rendered output: `cache.get_or_set(key, callable, timeout)` computes and stores in one call
The four levels, narrowest last
- Per-site middleware — the entire page, for every GET/HEAD that returned 200 — the largest thing that can go stale
- @cache_page on one view — one URL's full response, keyed by path and query string
- {% cache %} template fragment — one block; the rest of the page still renders per request, so personalisation survives
- cache.get_or_set() in Python — one value — an aggregate, an API response, a permission set. The only level with precise invalidation.
- The queryset itself — no cache at all — often the right answer once select_related and an index are in place
Command
from django.core.cache import cache
value = cache.get_or_set("leaderboard:v1", compute_leaderboard, 300)Four levels, from the whole site down to a single value, and the right one is the narrowest that removes the cost you measured. Per-site and per-view only cache `GET`/`HEAD` 200 responses and key on the URL — which makes per-site caching wrong for anything personalised. Fragment caching keeps the rest of the page live; the low-level API is the only level that caches a value rather than output, and the only one with precise invalidation. Always `list()` a queryset before caching it.