A Django inclusion tag's template cannot see request.user even though the page can; why, and how should takes_context be used to fix it?
answer
- a brand-new context
- only the returned mapping
- one variable is copied across
- context must be the first parameter
basics
~10 sAn inclusion tag renders its template with a fresh context holding only the dictionary its function returns, plus csrf_token. Use takes_context=True, name the first parameter context, and return the values the fragment needs.
solid answer
~40 s`InclusionNode.render` calls the function, then renders the fragment with `context.new(result)`: a new context holding **only** the returned dictionary, with `csrf_token` copied over because inclusion tags often render forms. Context-processor variables such as `request`, `user` and `perms` from the page are not there. The fix is `@register.inclusion_tag("articles/related_box.html", takes_context=True)` with a function whose first parameter is named `context`, returning what the fragment needs, for example `{"related": ..., "request": context["request"]}`. Prefer passing explicit arguments when the tag needs one or two values: `takes_context` couples the tag to whatever names the page's context happens to use, and a missing key raises `KeyError` inside the function. `simple_tag` takes the same option but renders nothing itself, so the fresh-context issue is specific to inclusion tags.
code
django · 11 lines{# articles/related_box.html, rendered by the inclusion tag #}
<aside>
{% for item in related %}
<a href="{{ item.get_absolute_url }}">{{ item.title }}</a>
{% if can_bookmark %}
<form method="post" action="{% url 'bookmark-add' item.pk %}">
{% csrf_token %}<button>Save</button>
</form>
{% endif %}
{% endfor %}
</aside>go deeper
Remember that an inclusion tag's template only sees what the tag function returns, and that csrf_token is the one variable copied in automatically.
Explain context.new, why context processor variables vanish, and how takes_context with a first parameter named context lets the function forward values.
Diagnose the empty fragment quickly, choose explicit arguments over takes_context where possible, and watch for per-call queries when tags sit inside loops.
Define conventions for shared tags: declared inputs, no hidden dependence on page context names, and tests that render each tag in isolation.
## The symptom A related-articles box is implemented as an **inclusion tag**. The page shows the logged-in user's name, but inside the box `{% if request.user.is_authenticated %}` is always false and `{{ user.username }}` is empty. There is no error: missing variables simply resolve to nothing. ## Why it happens An inclusion tag works in three steps, visible in `django/template/library.py`: 1. Django resolves the tag's arguments and **calls your function**. 2. It loads the fragment template, once per render, even inside a loop. 3. It renders the fragment with **`context.new(result)`**, a new context that keeps the engine settings, such as autoescaping, but holds **only the dictionary your function returned**. The page's variables, including everything added by **context processors** (`request`, `user`, `perms`, `messages`), are not copied. The only exception is **`csrf_token`**, which Django copies across explicitly because inclusion tags often render forms and CSRF protection should keep working without extra effort. This isolation is deliberate: a fragment that declares its inputs in one dictionary is predictable and reusable on any page. ## The fix: `takes_context=True` ```python from django import template from articles.models import Article register = template.Library() @register.inclusion_tag("articles/related_box.html", takes_context=True) def related_articles(context, article, limit=3): request = context["request"] related = ( Article.objects.filter(topic=article.topic, is_published=True) .exclude(pk=article.pk) .order_by("-published_at")[:limit] ) return { "related": related, "request": request, "can_bookmark": request.user.is_authenticated, } ``` Rules for `takes_context`: - **The first parameter must be named `context`.** Django passes the current template context there; the tag's own arguments follow. - **Template callers do not pass it.** `{% related_articles article limit=5 %}` is unchanged. - **Copy only what the fragment needs.** Returning `can_bookmark` rather than the whole request keeps the fragment's inputs explicit. - **The request is there only if something put it there**, normally the `django.template.context_processors.request` processor with a request-based render. Otherwise `context["request"]` raises `KeyError`, a server error. ## Explicit arguments versus `takes_context` | Approach | Template call | Coupling | Failure when a value is missing | |---|---|---|---| | explicit arguments | `{% related_articles article request.user %}` | visible at every call site | missing variable arrives as the empty `string_if_invalid` | | `takes_context=True` | `{% related_articles article %}` | tag depends on names in the page context | `KeyError` in the function, unless you use `context.get()` | Use explicit arguments for one or two values; use `takes_context` when a tag needs several context values that every page provides anyway, such as the request. ## Related traps - **`simple_tag` with `takes_context`.** The same option exists, and the function also receives `context` first. A `simple_tag` does not render a separate template, so there is no fresh-context problem; the risk there is only coupling. - **Queries per call.** An inclusion tag's function runs on every use. Placing `{% related_articles item %}` inside a loop over twenty articles runs twenty queries; precompute in the view or restructure. - **Escaping still applies.** The fragment renders with the parent's autoescape setting, so values from the dictionary are escaped as usual. - **Do not mutate the passed context.** Return a new dictionary rather than writing into `context`, so the page's variables are unaffected. ## Why Django isolates the fragment The fresh context is a design choice, not an oversight: - **Reusability.** A fragment that depends only on its returned dictionary renders the same on every page, whatever that page's view put in the context. - **No accidental leaks.** Variables from the page, such as a form bound to another object or a loop variable with a common name like `item`, cannot bleed into the fragment and change its output. - **Explicit contracts.** Reading the tag function tells you everything the template can use, which makes review and testing straightforward. `{% include %}` behaves differently: by default an included template sees the whole current context, and only `only` restricts it. Candidates who expect inclusion tags to behave like `{% include %}` are the ones who hit this bug. ## How to confirm the diagnosis 1. Temporarily print `{{ request }}` inside the fragment: it is empty. 2. Check the tag's registration: no `takes_context=True`, or the value is not returned. 3. After the fix, add a test that renders a page using the tag for an authenticated user and asserts the user-specific markup appears.
- Why does {% csrf_token %} work inside an inclusion tag's template when request does not?Django copies one variable explicitly: after building the fragment's new context from your dictionary, `InclusionNode.render` sets `csrf_token` from the parent context if present, because inclusion tags often render forms. Nothing else from the parent is copied, so `request`, `user` and `perms` must be passed in the returned dictionary.
- When is takes_context the wrong choice?When the tag needs one or two values that a call site can pass explicitly. `takes_context` hides the dependency: the tag silently requires certain names in every page's context and raises `KeyError` where they are missing. Explicit arguments make the inputs visible and the tag easier to test and reuse.
An inclusion tag is a contractor who builds a component offsite: the contractor sees only the materials in the box you hand over, not the rest of your house. takes_context lets the contractor look around the house first, but you still have to pack what they need into the box.
saying these in an interview costs you the question
- An inclusion tag's template inherits every variable of the calling page
- takes_context passes the whole page context into the fragment automatically
- The context parameter can have any name as long as it comes first
- csrf_token must always be passed manually to inclusion tag templates
- Template callers must pass context as the first tag argument