skip to content

In a Django template, why does {{ student.profile.advisor }} render blank when the profile is missing while a bug inside a property crashes the page?

level: seniorimportance: nice to knowfreq 22%

answer

  1. failed lookup versus raised exception
  2. an exception-class flag
  3. every DoesNotExist carries it
  4. property errors are re-raised

basics

~20 s

An exception raised by code a lookup runs, such as a property or method, propagates unless its class sets silent_variable_failure = True. ObjectDoesNotExist sets it, so a missing related object renders string_if_invalid; an ordinary AttributeError or ZeroDivisionError inside a property or method stops the render.

solid answer

~40 s

The Django template engine separates a **failed lookup** from **code that raised**. A lookup where the key, attribute and index all fail becomes `VariableDoesNotExist` and renders `string_if_invalid`. If resolving the variable runs code, a property or a called method, and that code raises, the exception propagates unless it has `silent_variable_failure = True`. `ObjectDoesNotExist` carries that flag, so every model's `DoesNotExist`, including the `RelatedObjectDoesNotExist` a missing reverse one-to-one raises, renders blank. A plain `AttributeError` raised inside a property is deliberately re-raised rather than treated as a missing attribute, so a typo inside the property's code surfaces as an error. You can make your own exception silent by setting the flag on its class.

code

python · 17 lines
python
from django.template import Context, Template

class Missing(Exception):
    silent_variable_failure = True

class Student:
    @property
    def advisor(self):
        raise Missing
    @property
    def gpa(self):
        return self.points / 3  # AttributeError: no 'points'

t1 = Template("[{{ s.advisor }}]")
print(t1.render(Context({"s": Student()})))  # []
t2 = Template("[{{ s.gpa }}]")
t2.render(Context({"s": Student()}))          # raises AttributeError

go deeper

for a junior

Know that a missing related object usually renders blank in a Django template, while an error inside your own model code can still break the page.

for a middle

Explain silent_variable_failure, that ObjectDoesNotExist carries it, and that other exceptions from properties and methods propagate.

for a senior

Diagnose both symptoms in production: a blank field from a missing related row versus a render error from a broken property, and choose explicit handling in the view over silencing.

for a principal

Decide where absence is modelled: explicit states computed in views and tested, rather than relying on template silence that hides both legitimate gaps and bugs.

## Two kinds of failure When the **Django template language** resolves `{{ student.profile.advisor }}`, two different things can go wrong: 1. **A lookup fails.** No dictionary key, attribute or list index matches a segment. The engine raises `VariableDoesNotExist` internally and renders the engine's `string_if_invalid` option, an empty string by default. 2. **Code raises.** The lookup reaches a property, a descriptor or a callable, runs it, and that code raises an exception. The engine catches it, checks one attribute, and either renders `string_if_invalid` or re-raises. The attribute is **`silent_variable_failure`**. If the exception, or its class, has `silent_variable_failure = True`, the variable renders as `string_if_invalid`; otherwise the exception propagates and the render fails, which in production means a server error for that request. ## Why a missing profile is silent Django sets `silent_variable_failure = True` on `django.core.exceptions.ObjectDoesNotExist`, the base class of every model's `DoesNotExist`. When a related object is absent, a reverse one-to-one accessor raises `RelatedObjectDoesNotExist`, a class Django builds to inherit from both the related model's `DoesNotExist` and `AttributeError`. It therefore carries the flag, and `{{ student.profile.advisor }}` renders as `string_if_invalid`. The documentation states the consequence directly: with Django templates and model objects, any `DoesNotExist` exception fails silently. ## Why a bug inside a property is loud Consider a property with a typo inside it: ```python from django.db import models class Student(models.Model): first_name = models.CharField(max_length=50) total_points = models.PositiveIntegerField(default=0) credits = models.PositiveIntegerField(default=0) @property def gpa(self): return self.total_points / self.credts # typo: AttributeError ``` `{{ student.gpa }}` runs the property, which raises `AttributeError`. The engine's attribute-lookup step catches `AttributeError`, but before falling through to list-index lookup it checks whether the name exists on the object (`gpa` in `dir(student)`). It does, so the engine concludes the error came from inside the property and **re-raises** it. `AttributeError` has no `silent_variable_failure`, so the render fails with the real traceback. The same holds for a `ZeroDivisionError` when `credits` is zero, and for a method that raises when it is called. ## The rules side by side | What happens during resolution | Result | |---|---| | no key, attribute or index matches | `string_if_invalid` | | code raises an exception whose class sets `silent_variable_failure = True` (any `DoesNotExist`) | `string_if_invalid` | | a property raises `AttributeError` for a name the object has | the exception propagates | | a called method raises any other exception | the exception propagates | | a callable needs arguments | `string_if_invalid` | | an invalid variable inside `{% if %}` or `{% for %}` | treated as `None` | ## Making your own exception silent Setting the flag on a class is supported and documented: ```python class GradeNotPublished(Exception): silent_variable_failure = True ``` A method that raises `GradeNotPublished` then renders as `string_if_invalid` instead of failing. Use this sparingly: it turns an error into a blank, which is the same trade-off that makes typos invisible. ## Production judgement - **A blank advisor field is data, not necessarily a bug.** If "no profile" is a legitimate state, render it explicitly with `{% if student.profile %}` or precompute it in the view so the page says "No advisor assigned" instead of an empty cell. - **A crash inside a property is a gift.** It points to a real bug; do not wrap properties in broad `try/except` blocks to make templates quiet. - **Debug output.** Every exception caught during resolution is logged at `DEBUG` level by the `django.template` logger, with the template name, which helps find silent `DoesNotExist` cases in development. - **Keep heavy logic out of properties that templates touch.** A property that queries or computes can raise for reasons the template cannot handle; computing in the view makes failures explicit and testable. ## Telling the cases apart 1. **The page fails to render** and the traceback ends in your model code: an exception without the silent flag escaped from a property or method; fix the code. 2. **A field is blank and the object exists**: check the name for a typo, then check whether the callable needs arguments or is flagged with `alters_data`. 3. **A field is blank and a related row is missing**: a `DoesNotExist` was swallowed; decide whether absence deserves an explicit message.

  • Does a forward ForeignKey with null=True also rely on silent_variable_failure?
    No. When a nullable forward foreign key is empty, the accessor returns `None` rather than raising, so `{{ mark.moderator.name }}` fails as an ordinary lookup on `None` and renders `string_if_invalid`. The silent exception path matters for accessors that raise, such as a reverse one-to-one with no related row.
  • Why re-raise an AttributeError from a property instead of treating it as a missing attribute?
    Because the name exists: the object has a `gpa` property, so an `AttributeError` coming out of it means the property's own code is broken. Swallowing it would hide a real bug behind a blank value. The engine only falls through to list-index lookup when the name is genuinely absent from the object.

saying these in an interview costs you the question

  • Every exception raised while rendering a variable is silenced
  • A missing reverse one-to-one object crashes the template
  • silent_variable_failure is a setting in TEMPLATES OPTIONS
  • An AttributeError inside a property is treated like a missing attribute
  • Wrapping properties in try/except is the right way to quiet templates