skip to content

How does Django's {% cache %} template tag cache a fragment per user or language, and how do you invalidate that fragment from Python code?

level: middleimportance: must knowfreq 48%

answer

  1. load it first
  2. timeout, name, then vary_on
  3. arguments become strings
  4. rebuild the key to delete it

basics

~20 s

Django's {% cache timeout name var1 var2 %} stores the rendered block under a key from the fragment name and the extra arguments' string values. To invalidate, rebuild it with make_template_fragment_key(name, [vars]) and delete it from the same alias.

solid answer

~40 s

After `{% load cache %}`, `{% cache 600 leaderboard_panel request.user.username LANGUAGE_CODE %}` renders the block once and stores the HTML. The first argument is the timeout in seconds (an integer or a variable; `None` caches forever), the second is a literal fragment name, and every further argument is a **vary_on** value converted to a string and hashed into the key, so each user and language gets its own copy. The tag uses the `template_fragments` alias if `CACHES` defines one, otherwise `default`, or `using="alias"` as the last argument. To invalidate from Python, `django.core.cache.utils.make_template_fragment_key("leaderboard_panel", [username, lang])` rebuilds the key, and `delete()` on the same alias removes it. Vary values must be unique as strings: a model instance or `None` versus `"None"` can collide.

code

django · 10 lines
django
{% load cache i18n %}
{% get_current_language as LANGUAGE_CODE %}

{% cache 600 leaderboard_panel request.user.pk LANGUAGE_CODE %}
  <ol class="leaderboard">
    {% for row in leaderboard %}
      <li>{{ row.player }} - {{ row.points }}</li>
    {% endfor %}
  </ol>
{% endcache %}

go deeper

for a junior

Recall {% load cache %}, the timeout-then-name order, and that extra arguments give separate copies.

for a middle

Explain how vary_on values are stringified into the key, which alias is used, and how make_template_fragment_key invalidates.

for a senior

Avoid key collisions and exploding key spaces, and design invalidation for per-user fragments with versioned vary_on values.

for a principal

Decide where caching belongs, markup or data, so templates, APIs and invalidation share one source of truth.

## What fragment caching is for Whole-page caching fails as soon as a page contains anything personal. The **`{% cache %}`** template tag caches just a **block** of a template: an expensive leaderboard panel, a sidebar, a menu. The rest of the page still renders normally for each request. It lives in the `cache` tag library, so the template needs `{% load cache %}` first. ## Arguments, in order ```django {% load cache %} {% cache 600 leaderboard_panel request.user.username LANGUAGE_CODE using="fragments" %} ... expensive rendering ... {% endcache %} ``` | Position | Meaning | Notes | |---|---|---| | 1 | Timeout in seconds | Integer or template variable resolving to one; `None` caches forever | | 2 | Fragment name | Taken literally; it cannot be a variable | | 3 and on | **vary_on** values | Variables or literals, filters allowed; each is converted to a string | | last, optional | `using="alias"` | Which `CACHES` alias to use | A timeout that is not an integer raises a `TemplateSyntaxError` at render time, and so does naming an alias that is not configured. ## How the key is built `django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on)` produces: ``` template.cache.<fragment_name>.<md5 of the vary_on values> ``` Each vary_on value is converted with `str()` and fed into the hash with a length delimiter. The alias's key function then adds `KEY_PREFIX` and `VERSION` as for any other key. Consequences: - **Per-user copies**: include something unique per user, such as `request.user.username` or `request.user.pk`. - **Per-language copies**: unlike the page cache, fragments do **not** vary by language automatically. Use `{% get_current_language as LANGUAGE_CODE %}` from the `i18n` library and pass `LANGUAGE_CODE`. - **String collisions**: `None` and `"None"` produce the same key; a model instance's `__str__()` may not be unique, so pass a primary key or slug instead. ## Which cache it writes to 1. `using="alias"` if given. 2. Otherwise an alias named **`template_fragments`**, if `CACHES` defines one. 3. Otherwise **`default`**. This matters for invalidation: deleting the key from `cache` (the default alias) does nothing if the fragment lives in `template_fragments`. ## Invalidating a fragment When a match result changes the leaderboard, rebuild the key with the same name and vary_on values and delete it: ```python from django.core.cache import caches from django.core.cache.utils import make_template_fragment_key key = make_template_fragment_key("leaderboard_panel", [username, "en"]) caches["fragments"].delete(key) ``` For a fragment that varies per user, you cannot enumerate every user's key cheaply. The practical options are a short timeout, or putting a changing value into vary_on, for example the leaderboard's last-updated timestamp, so a change produces new keys and old copies simply expire. ## Version note Django 6.1 changed how vary_on values are hashed into fragment keys (each value is now length-delimited). After upgrading, the first render of every fragment that varies on arguments is a miss; fragments with no vary_on keep their keys. ## Common mistakes - **Forgetting `{% load cache %}`**: the template fails with an invalid block tag error. - **Putting a variable in the name slot**: the second argument is taken literally, so `{% cache 600 panel_name %}` caches under the name `panel_name`, not under the variable's value. - **Caching a per-user fragment without a per-user vary_on**: the first user's panel is shown to everybody, exactly like a leaked page. - **Vary values that stringify identically**: `None` versus `"None"`, or two model instances with the same `__str__()`. - **Invalidating on the wrong alias** when a `template_fragments` alias exists. - **Caching fragments that include a CSRF token**: a form's `{% csrf_token %}` inside a cached block freezes one token into the markup for everyone; keep forms outside cached blocks. - **Nesting cached fragments without thinking about timeouts**: an outer fragment with a long timeout keeps serving the old inner content even after the inner one is invalidated. ## When to prefer the low-level API - The expensive part is **data**, not markup, and several templates or an API also need it: cache the data with `cache.get_or_set()` in the view. - The fragment depends on many variables: the key space explodes and the hit rate falls.

  • Why can cache.delete(make_template_fragment_key(...)) fail to invalidate a fragment?
    Either the key was rebuilt with different vary_on values (order, type or string form differ), or the delete went to the wrong alias. The tag writes to `template_fragments` when that alias exists, so deleting from `django.core.cache.cache`, the default alias, removes nothing.
  • Does {% cache %} vary by the active language automatically?
    No. The whole-page cache appends the language when `USE_I18N` is on, but the fragment tag only varies on the arguments you pass. Load `i18n`, use `{% get_current_language as LANGUAGE_CODE %}`, and pass `LANGUAGE_CODE` as a vary_on argument.

saying these in an interview costs you the question

  • The fragment name in {% cache %} can be a template variable.
  • {% cache %} automatically keeps a separate copy per logged-in user.
  • Deleting from the default cache always invalidates a fragment.
  • Passing a model instance as vary_on is always safe and unique.
  • {% cache 0 name %} caches the fragment forever.