skip to content

Why does a Django child template that uses {% extends %} silently drop markup placed outside its {% block %} tags?

level: middleimportance: should knowfreq 38%

answer

  1. the parent is what renders
  2. the child contributes only blocks
  3. blocks are collected before logic runs
  4. each file loads its own libraries
  5. extends goes before every tag

basics

~10 s

Rendering a child renders its parent; the child contributes only its {% block %} overrides, so text, tags and variables placed outside any block in the child are never output.

solid answer

~40 s

When a template starts with `{% extends %}`, Django collects the child's `{% block %}` nodes and renders the **parent**, dropping in those overrides. Nothing else in the child is rendered, so a `<p>` or an `{% if %}` outside any block silently disappears. The same mechanism explains related traps: a `{% block %}` wrapped in `{% if %}` always overrides, because blocks are collected regardless of surrounding tags; a variable set outside a block with an `as` clause is not visible inside it; tag libraries loaded in the parent are not loaded in the child, which needs its own `{% load %}` placed after `extends`; and anything other than text before `{% extends %}` raises `TemplateSyntaxError`. Two blocks with the same name in one file are also a parse error.

code

django · 15 lines
django
{% extends 'base.html' %}
{% load booking_tags %}

{% block banner %}
  {% if user.is_staff %}
    Staff view
  {% else %}
    {{ block.super }}
  {% endif %}
{% endblock banner %}

{% block content %}
  <p class="notice">Free cancellation until 24h before departure</p>
  {% for booking in bookings %}...{% endfor %}
{% endblock content %}

go deeper

for a junior

Remember that in a child template only the contents of blocks are shown, and that extends must be the first tag in the file.

for a middle

Explain the render order — blocks are collected, then the parent renders — and use it to predict the if-around-block and load-in-child traps.

for a senior

Diagnose a missing banner or notice from the template alone by spotting content outside blocks, and put conditions inside blocks with block.super as the fallback.

for a principal

Treat silent drops as a review risk: codify child-template conventions (extends, load, blocks only) so they are caught in review rather than in production.

## How a child template is actually rendered The key to every inheritance surprise is one fact: **rendering a child template renders its parent**. When Django parses a template whose first tag is `{% extends 'base.html' %}`, the rest of the file becomes the extends node's contents. At render time that node: 1. loads the parent template, 2. collects every `{% block %}` found anywhere in the child — including blocks nested inside other tags — and registers them as overrides, 3. renders the parent, which outputs its own markup and, at each block, the most-derived override. The child's own markup is never rendered on its own. That single rule explains the list below. ## Trap 1: markup outside blocks vanishes ```django {% extends 'base.html' %} <p class="notice">Free cancellation until 24h before departure</p> {% block content %}...{% endblock %} ``` The `<p>` is not an error and not a warning; it is simply never output. The fix is to move it inside a block the parent renders, or to add a new block to the parent for it. ## Trap 2: a block inside `{% if %}` always overrides ```django {% if user.is_staff %} {% block banner %}Staff view{% endblock %} {% endif %} ``` The `{% if %}` sits outside every block, so it never runs; the `banner` block inside it is still collected in step 2. Every user sees "Staff view". Django's documentation states that blocks are evaluated first, so the override applies regardless of the truthiness of surrounding tags. The fix is to put the condition **inside** the block: ```django {% block banner %}{% if user.is_staff %}Staff view{% else %}{{ block.super }}{% endif %}{% endblock %} ``` ## Trap 3: variables set outside a block are invisible inside it A tag that stores its result with `as` (for example a tag that computes a value `as title`) placed outside a block in a child never executes, so the block sees no such variable. Compute it inside the block that uses it. ## Trap 4: each file loads its own tag libraries `{% load %}` works at **parse time**, per file. A library the parent loads is not available to the child's parser, so the child must load it too — and because `{% extends %}` must be the first template tag, the child's `{% load %}` goes **after** `extends`. A `{% load %}` before `extends` raises `TemplateSyntaxError` with the message that extends must be the first tag. ## Trap 5: parse-time errors worth recognising | Symptom | Cause | |---|---| | `{% extends ... %} must be the first tag` | a tag (often `{% load %}`) precedes `extends` | | `'block' tag with name '...' appears more than once` | two blocks with the same name in one file | | `'extends' cannot appear more than once` | a second `extends` in the same template | | `Invalid template name in 'extends' tag` | `extends` given a variable that resolved to an empty value | Duplicate block names are forbidden because a block works in both directions: it is a hole the file's own children can fill and the content that fills the hole in its parent. With two same-named blocks, the parent could not tell which one to use. ## Checklist for a child template - `{% extends %}` first; `{% load %}` for this file's tag libraries next. - Everything visible lives inside a block the parent renders. - Conditions go inside blocks, never around them. - Each block name appears once per file. Follow that and a child template becomes what it should be: a list of named overrides and nothing else.

  • The parent base.html loads a custom tag library; why does the child still fail with an invalid block tag error?
    `{% load %}` registers tags on the parser of the file it appears in. Each template is parsed separately, so the child's parser has never seen the library. Add `{% load %}` to the child, right after `{% extends %}`.
  • How do you show a block's override only for some users?
    Put the condition inside the block: `{% block banner %}{% if user.is_staff %}...{% else %}{{ block.super }}{% endif %}{% endblock %}`. The block is always collected, so the choice has to happen while the block renders, with `block.super` supplying the parent's default in the other branch.

saying these in an interview costs you the question

  • Markup outside blocks in a child is rendered above the parent's output.
  • Wrapping a block in {% if %} makes the override conditional.
  • A child can use the tag libraries its parent already loaded.
  • {% load %} may come before {% extends %} because it only registers tags.
  • Django warns when child markup falls outside every block.