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?
answer
- failed lookup versus raised exception
- an exception-class flag
- every DoesNotExist carries it
- property errors are re-raised
basics
~20 sAn 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 sThe 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 linesfrom 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 AttributeErrorgo deeper
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.
Explain silent_variable_failure, that ObjectDoesNotExist carries it, and that other exceptions from properties and methods propagate.
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.
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