In Django templates, which operators can {% if %} use, how are and and or grouped without parentheses, and when does {% with %} help?
answer
- comparison and membership words
- no brackets allowed
- and binds tighter
- name an expensive lookup once
basics
~20 s{% if %} supports and, or, not, comparisons, in, not in, is and is not, with filtered operands. Parentheses are invalid and and binds tighter than or, so nest tags for other grouping. {% with %} names an expensive lookup once.
solid answer
~50 sDjango's `{% if %}` evaluates truthiness and supports `{% elif %}` and `{% else %}`. Its operators are `and`, `or`, `not`, the comparisons `==`, `!=`, `<`, `>`, `<=`, `>=`, membership `in` and `not in`, and identity `is` and `is not`; filters work on operands, as in `{% if cart.items|length > 5 %}`. Parentheses are invalid syntax: `and` binds more tightly than `or`, so `a and b or c` means `(a and b) or c`, and any other grouping needs nested `{% if %}` tags or a value computed in the view. A missing variable is treated as `None`, so `{% if typo %}` is quietly false. `{% with total=order.lines.count %}...{% endwith %}` evaluates an expression once and binds it to a name for the enclosed block, which avoids repeating an expensive method call such as a `count()` query.
code
django · 6 lines{% with lines=order.lines.count %}
{% if order.is_paid and lines > 0 or order.is_draft %}
<p>{{ lines }} line{{ lines|pluralize }} ready.</p>
{% endif %}
{% if order.coupon is None %}<p>No coupon applied.</p>{% endif %}
{% endwith %}go deeper
Know if, elif and else, the comparison and boolean operators, and that {% with %} gives an expression a short name inside its block.
Explain the precedence of and over or, why parentheses fail, what is versus == distinguishes, and how {% with %} avoids repeating an expensive call.
Push complex conditions into view flags or model properties, watch for repeated query-backed lookups, and recognise silent false conditions caused by missing variables.
Keep template logic to presentation decisions and make business rules first-class in Python, so reviewers can find and test them.
## The `{% if %}` tag `{% if %}` in the **Django template language** renders its block when the condition is **truthy** in the Python sense: non-empty strings and collections, non-zero numbers, `True`, and objects that do not define themselves as false. It supports any number of `{% elif %}` branches and one `{% else %}`: ```django {% if order.is_paid %} Paid {% elif order.is_overdue %} Overdue {% else %} Awaiting payment {% endif %} ``` ## Operators and precedence The tag parses its own small expression language (in `django/template/smartif.py`). From loosest to tightest binding: | Precedence | Operators | Example | |---|---|---| | lowest | `or` | `{% if a or b %}` | | | `and` | `{% if a and b %}` | | | `not` | `{% if not a %}` | | | `in`, `not in` | `{% if "vip" in user_tags %}` | | highest | `is`, `is not`, `==`, `!=`, `<`, `>`, `<=`, `>=` | `{% if total >= 100 %}` | Consequences worth knowing: 1. **`and` beats `or`.** `{% if staff and active or preview %}` means `(staff and active) or preview`. 2. **Parentheses are invalid syntax.** Django's documentation says to nest `{% if %}` tags when you need a different grouping. 3. **Filters apply to operands**: `{% if order.lines.all|length > 10 %}` compares the filtered value. 4. **`is` tests identity**, useful for `{% if value is None %}` or `{% if flag is True %}`, where `==` and truthiness would blur `0`, `False` and `None`. 5. **Missing variables become `None`.** A misspelled name does not raise; it behaves as `None`, so `{% if typo %}` is simply false. ## Operators that raise evaluate to false The infix operators swallow exceptions. If evaluating `{% if total > limit %}` raises, for example because `limit` is a string and Python refuses to compare it with an integer, or `{% if code in allowed %}` where `allowed` does not support `in`, the operator returns `False` instead of failing the render. The source comment says it plainly: templates should not throw exceptions while rendering. The consequence is the same as with missing variables: - **A type mismatch hides silently.** The branch never renders and nothing is logged as an error. - **Tests must assert the rendered branch**, not only that the page returns 200. - **Types belong in the view.** Pass integers as integers and collections as collections, and the comparisons behave like Python's. A bare truth test such as `{% if order.is_paid %}` has no operator, so an exception raised by a property it calls is not swallowed by this rule. ## Where logic stops belonging in the template The expression language is deliberately small: no arithmetic, no method calls with arguments, no function definitions. When a condition needs parentheses or combines more than two or three facts, the clearer fix is usually a property or a context variable computed in the view, such as `can_refund`, so the template reads `{% if can_refund %}` and the rule is tested in Python. ## The `{% with %}` tag `{% with %}` **evaluates an expression once and binds it to a name** for the block up to `{% endwith %}`: ```django {% with total=order.lines.count %} {{ total }} line{{ total|pluralize }} {% if total > 20 %}(large order){% endif %} {% endwith %} ``` Without it, `order.lines.count` would be looked up twice, and because the engine calls `count()` each time, that is **two `COUNT` queries**. With `{% with %}`, the value is computed once. Other points: - **Several names at once**: `{% with net=order.net gross=order.gross %}`. - **Scope is the block.** The name exists only between `{% with %}` and `{% endwith %}` and disappears afterwards. - **Legacy form.** `{% with order.lines.count as total %}` still works; the `name=value` form is the current style. - **It caches, it does not compute.** `{% with %}` cannot do arithmetic or call methods with arguments; it only names the result of an ordinary variable lookup, filters included. ## Common interview traps - Writing `{% if (a or b) and c %}` and expecting it to parse: it raises a template syntax error. - Comparing a string from the URL with a number: `{% if request.GET.page == 1 %}` is false for the string `"1"`; convert in the view. - Expecting `{% with %}` to leak its name after `{% endwith %}`.
- How do you express (a or b) and c in a Django {% if %} tag?Parentheses are invalid, so either nest the tags, `{% if c %}{% if a or b %}...{% endif %}{% endif %}`, or compute the condition in the view or as a model property and test that single name. Computing it in Python is usually better because the rule becomes testable and reusable.
- Why does {% if request.GET.page == 1 %} never match on page one?Query parameters are strings, and the literal `1` in the tag is an integer, so the comparison is `"1" == 1`, which is false. The if tag does no type coercion. Compare with a quoted literal, `== "1"`, or better, convert the value in the view and pass a proper integer such as `page_obj.number`.
saying these in an interview costs you the question
- Parentheses can group conditions inside {% if %}
- or binds more tightly than and in Django templates
- A misspelled variable in {% if %} raises an error
- {% with %} variables remain available after {% endwith %}
- {% with %} can do arithmetic such as total=a+b