Built-in Django tags

Django offers several built-in tags that offer immediate access to elaborate operations on Django templates. Unlike Django filters which operate on individual variables, tags are designed to produce results without a variable or operate across template sections.

I'll classify each of these built-in tags into functional sections so it's easier to identify them. The functional classes I'll use are: Dates, forms, comparison operations, loops, Python & filter operations, spacing and special characters, template structures, development and testing & urls.

Dates

Tip The {% now %} tag can accept Django date variables: {% now "DATE_FORMAT" %}, {% now "DATETIME_FORMAT" %}, {% now "SHORT_DATE_FORMAT" %} or {% now "SHORT_DATETIME_FORMAT"}.The date variables in themselves are also composed of date strings. For example DATE_FORMAT default's to "N j, Y" (e.g. Jan 1, 2022), DATETIME_FORMAT defaults to "N j, Y, P" (e.g. Jan 1, 2022, 12 a.m.), SHORT_DATE_FORMAT defaults to "m/d/Y" (e.g. 01/01/2022) and SHORT_DATETIME_FORMAT defaults to "m/d/Y P" (e.g. 01/01/2022 12 a.m.). Each date variable can be overridden with different date strings in a project's settings.py file.

Forms

Comparison operations

Listing 3-18. Django {% if %} tag with {% elif %} and {% else %}

{% if drinks %}             {% if drinks %}              {% if drinks %}
  We have drinks!                We have drinks              We have drinks 
{% endif %}                 {% else %}                   {% elif drinks_on_sale %}
                                No drinks,sorry              We have drinks on sale!
                            {% endif %}                  {% else %}
                                                           No drinks, sorry 
                                                         {% endif %}
Note A variable must both exist and not be empty to evaluate to true. A variable that just exists and is empty evaluates to false.

Listing 3-19. Django {% firstof %} tag and equivalent {% if %}{% elif %}{% else %} tags

# Firstof example
{% firstof var1 var2 var3 %} 
# Equivalent of firstof example
{% if var1 %}
    {{var1|safe}}
{% elif var2 %}
    {{var2|safe}}
{% elif var3 %}
    {{var3|safe}}
{% endif %}
# Firstof example with a default value in case of no match (i.e, all variables are empty) 
{% firstof var1 var2 var3 "All vars are empty" %} 
# Assign the firstof result to another variable 
{% firstof var1 var2 var3 as resultof %}
# resultof now contains result of firstof statement
Parenthesis are not allowed in {% if %} tags: Operator precedence governs, use nested {% if %} tags to alter precedence

Comparison operators are often aggregated into single statements (e.g. if...<...or...>...and...==...) and follow a certain execution precedence. Django follows the same operator precedence as Python[5]. So for example, the statement {% if drink in specials or drink == drink_of_the_day %} gets evaluated as ((drink in specials) or (drink == drink_of_the_day)) , where the internal parenthesis operations are run first, since in and == have higher precedence than or.

In Python you can alter this precedence by using explicit parenthesis in comparison statements. However, Django does not support the use of parenthesis in {% if %} tags, you must either rely on operator precedence or use nested {% if %} statements to declare the same logic produced by explicit parenthesis.

Loops

Listing 3-20 Django {% for %} tag and {% for %} with {% empty %}

<ul>                                 <ul>
{% for drink in drinks %}             {% for storeid,store in stores %}
 <li>{{ drink.name }}</li>            <li><a href="/stores{{storeid}}/">{{store.name}}</a></li>
{% empty %}                           {% endfor %}
 <li>No drinks, sorry</li>           </ul>
{% endfor %}
</ul>

The {% for %} tag also generates a series of variables to manage the iteration process, such as an iteration counter, a first iteration flag and a last iteration flag. These variables can be useful when you want to create behaviors (e.g. formatting, additional processing) on a given iteration. Table 3-4 illustrates the {% for %} tag variables.

Table 3-4 Django {% for %} tag variables

forloop.counter

The current iteration of the loop (1-indexed)

forloop.counter0

The current iteration of the loop (0-indexed)

forloop.revcounter

The number of iterations from the end of the loop (1-indexed)

forloop.revcounter0

The number of iterations from the end of the loop (0-indexed)

forloop.first

True if it's the first time through the loop

forloop.last

True if it's the last time through the loop

forloop.parentloop

For nested loops, this is the parent loop to the current one

Listing 3-21 Django {% cycle %} with explicit control of progression

<li class="{% cycle 'disc' 'circle' 'square' as bullettype %}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{% cycle bullettype %}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{% cycle bullettype %}">...</li>

# Outputs
<li class="disc">...</li>
<li class="disc">...</li>
<li class="disc">...</li>
<li class="circle">...</li>
<li class="circle">...</li>
<li class="square">...</li>

As you can see in listing 3-21, the {% cycle %} tag statement initially produces the first value and afterwards you can continue using the cycle reference name to output the same value. In order to advance to the next value in the cycle, you call the {% cycle %} once more with the cycle reference name. A minor side-effect of the {% cycle %} tag is that it outputs its initial value where it's declared, something that can be problematic if you plan to use the cycle as a placeholder or in nested loops. To circumvent this side-effect, you can use the silent keyword after the cycle reference name (e.g.{% cycle 'disc' 'circle' 'square' as bullettype silent %}).

Listing 3-22 Django {% for %} tag and {% regroup %}

# Dictionary definition
stores = [
    {'name': 'Downtown', 'street': '385 Main Street', 'city': 'San Diego'},
    {'name': 'Uptown', 'street': '231 Highland Avenue', 'city': 'San Diego'},
    {'name': 'Midtown', 'street': '85 Balboa Street', 'city': 'San Diego'},
    {'name': 'Downtown', 'street': '639 Spring Street', 'city': 'Los Angeles'},
    {'name': 'Midtown', 'street': '1407 Broadway Street', 'city': 'Los Angeles'},
    {'name': 'Downton', 'street': '50 1st Street', 'city': 'San Francisco'},
]

# Template definition with regroup and for tags 
{% regroup stores by city as city_list %}
<ul>
{% for city in city_list %}
    <li>{{ city.grouper }}
    <ul>
        {% for item in city.list %}
          <li>{{ item.name }}: {{ item.street }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>

# Output
San Diego
    Downtown : 385 Main Street
    Uptown : 231 Highland Avenue
    Midtown : 85 Balboa Street
Los Angeles
    Downtown: 639 Spring Street
    Midtown: 1407 Broadway Street
San Francisco
    Downtown: 50 1st Street
Tip The {% regroup %} tag can also use filters or properties to achieve grouping results. For example, the stores list in 3-22 is conveniently pre-ordered by city making grouping by city automatic, but if the stores list were not pre-ordered, you would need to sort the list by city first to avoid fragmented groups, you can use a dictsort filter directly (e.g.{% regroup stores|dictsort:'city' by city as city_list %}). Another possibility of the {% regroup %} tag is to use nested properties if the grouping object has them (e.g. if city had a state property, {% regroup stores by city.state as state_list %}).

Python & filter operations

Python logic only allowed behind the scenes in custom Django tags or filters

Django templates don't allow the inclusion of inline Python logic. In fact, the closest thing Django templates allow to inline Python logic is through the {% with %} tag which isn't very sophisticated.

The only way to make custom Python logic work in Django templates is to embed the code inside a custom Django tag or filter. This way you can place a custom Django tag or filter on a template and the Python logic runs behind the scenes. The next section describes how to create custom Django filters.

Spacing and special characters

Tip If you want to enable or disable auto-escaping globally (i.e. on all templates), it's easier to disable it at the project level using the autoescape field in the OPTIONS variable in the TEMPLATES configuration, inside a project's settings.py file, as described in first section of this chapter.If you want to enable or disable auto-escaping on individual variables, you can either use the safe filter to disable auto-escaping on a single Django template variable or the escape filter to escape a single Django template variable.

Template structures

Tip If you find yourself using the {% load %} tag on many templates, you may find it easier to register Django tags and filters with the builtins option in TEMPLATES so they become accessible on all templates as if they where built-in. See the first section in this chapter on template configuration for more details.

Development and testing

Urls

  1. https://docs.python.org/3/reference/expressions.html#evaluation-order