In Django templates, when does the engine refuse to call a callable, and what do alters_data and do_not_call_in_templates each change?
answer
- called with zero arguments
- a flag against side effects
- a flag to leave it as an object
- save, delete, related managers
basics
~20 sDjango calls any callable it meets with no arguments. alters_data = True blocks the call and renders string_if_invalid, protecting methods like save() and delete(); do_not_call_in_templates = True leaves the object uncalled so its attributes stay reachable.
solid answer
~40 sAfter each lookup step, the Django template engine checks whether the value is callable and, if so, calls it with no arguments. Two attributes on the callable override that. `alters_data = True` makes the engine skip the call and render `string_if_invalid` instead, unconditionally; Django sets it on `Model.save()` and `delete()`, on `QuerySet.update()`, `delete()`, `create()`, `get_or_create()` and friends, and on related-manager methods such as `add()`, `remove()` and `clear()`, so `{{ student.delete }}` can never delete a row. `do_not_call_in_templates = True` makes the engine treat the object as not callable, so you can reach its attributes: Django's `Choices` enums and related managers carry it. A callable that needs arguments is not called either: it renders `string_if_invalid`.
code
python · 12 linesfrom django.template import Context, Template
class Report:
def summary(self):
return "3 subjects"
def send_to_parents(self):
raise RuntimeError("would send mail")
send_to_parents.alters_data = True
t = Template("[{{ r.summary }}] [{{ r.send_to_parents }}]")
print(t.render(Context({"r": Report()})))
# [3 subjects] []go deeper
Know that templates call methods without parentheses and without arguments, and that Django protects save() and delete() from being called by a template.
Explain both flags precisely: alters_data blocks the call and renders string_if_invalid, do_not_call_in_templates keeps the object so its attributes stay reachable.
Show the habit: mark your own mutating model and view methods with alters_data, remember that the view instance is in the context of generic views, and keep real authorisation in the view.
Treat template-reachable methods as an API surface: decide what domain objects expose to templates and whether view models or precomputed context reduce that surface.
## The default: every callable is called In the **Django template language**, the engine resolves `{{ a.b.c }}` one segment at a time. After each segment, if the value is **callable** (a function, a bound method, a class, or any object with `__call__`), the engine calls it **with no arguments** and carries on with the return value. That is what lets templates write `{{ student.get_full_name }}` or `{{ student.courses.all }}` without parentheses. Calling arbitrary code from a template is convenient and dangerous at the same time: a template author, or anything that can influence which variable a template prints, could trigger a method with side effects. Django therefore gives a callable three ways out of being called. ## The three ways a callable is not called | Situation | What the engine does | Typical examples | |---|---|---| | `alters_data = True` on the callable | skips the call and renders `string_if_invalid`, unconditionally | `Model.save()`, `Model.delete()`, `QuerySet.update()`, `QuerySet.delete()`, related-manager `add()`/`remove()`/`clear()`/`set()` | | `do_not_call_in_templates = True` | treats the value as not callable and keeps the object itself | `Choices` enum classes, related managers | | The callable requires arguments | the no-argument call fails the signature check, so it renders `string_if_invalid` | `mark_for(self, subject)` | ## alters_data: a guard against side effects `alters_data` is a plain function attribute the engine checks before calling. Django's documentation gives the canonical example: without it, `{{ data.delete }}` in a template would delete the record. Django marks its own mutating methods with it: - `Model.save()`, `Model.delete()` and their async twins `asave()` and `adelete()`; - `QuerySet.update()`, `QuerySet.delete()` and their async twins; - `QuerySet.create()`, `bulk_create()`, `get_or_create()`, `update_or_create()` and the async twins, plus `UserManager.create_user()` and `create_superuser()`, all added in **Django 5.2**; - related-manager methods such as `add()`, `create()`, `remove()`, `clear()` and `set()`. You should mark your own methods the same way: ```python from django.db import models class Student(models.Model): name = models.CharField(max_length=100) archived = models.BooleanField(default=False) def archive(self): self.archived = True self.save(update_fields=["archived"]) archive.alters_data = True ``` The same advice applies to class-based views: generic views put the view instance in the context as `view`, so a public method on the view is reachable as `{{ view.recalculate_grades }}`. Django's docs recommend `alters_data = True` on such methods. ## do_not_call_in_templates: keep the object Sometimes the callable is the thing you want to look inside, not call. Two built-in cases: 1. **`Choices` enums.** A `TextChoices` or `IntegerChoices` class is callable (calling it converts a value to a member), and Django sets `do_not_call_in_templates` on it, so `{{ Grade.A.label }}` reaches the member instead of calling the class with nothing. 2. **Related managers.** A reverse or many-to-many manager has a `__call__` that selects a different manager by name. The flag keeps the engine from invoking it, so `{{ student.enrolments.all }}` reaches the `all` method of the manager itself. Setting the flag on your own class works the same way: the engine behaves as if the value were not callable. ## Callables that need arguments The engine never invents arguments. It attempts the call; if that raises `TypeError`, it inspects the signature, and if arguments were required it substitutes `string_if_invalid`. If the signature accepts no arguments, the `TypeError` came from inside the method and is re-raised, so a genuine bug is not hidden. Passing arguments requires a custom filter or tag, or computing the value in the view. ## Why this matters in review - **Defence in depth, not access control.** `alters_data` stops accidental or template-driven side effects; it does not replace permissions in the view. - **Blank output is a clue.** If `{{ obj.method }}` renders nothing, check for a required argument or an `alters_data` flag before suspecting the data. - **Mark mutating helpers.** Any method that writes, sends mail or enqueues work and could end up in a template context deserves `alters_data = True`. - **Mind what the context exposes.** Passing a whole model instance, form or view into a template exposes every public method on it; each of those becomes callable from template text unless it is flagged or needs arguments. - **Prefer precomputed values for heavy methods.** A method that is safe to call but expensive, such as one that aggregates a term's marks, is still called on every render that mentions it, once per mention unless you store the result, for example with `{% with %}` or in the view.
- Is alters_data a security boundary?It is a safety net, not access control. It stops the template engine from calling a mutating method, which protects against accidents and against templates that can print arbitrary context variables. Who may change data is still decided in the view, with authentication and permission checks; a view that exposes an object to an untrusted template still needs those.
- Why do Django's related managers set do_not_call_in_templates?A related manager is callable: its `__call__` takes a `manager` keyword to switch to another manager of the related model. Without the flag the engine would try to call it with no arguments, fail, and render `string_if_invalid`, so `{{ student.enrolments.all }}` would break. The flag keeps the manager as an object so its `all` method is reachable.
saying these in an interview costs you the question
- Templates never call methods; you must precompute every value
- alters_data raises an exception when the template reaches the method
- do_not_call_in_templates hides the object from the template entirely
- alters_data replaces permission checks in the view
- {{ student.delete }} would delete the row if the author wrote it