Is it time to retire my favourite template tag?

Written by Dr Alastair Weakley
Published on 2 December 2024

Tagged under:

Django

About the author

Alastair co-founded the Interaction Consortium in 2009 and serves as one of the studio's Principal Developers. He has a degree in Design and Technology and after a first career as a product designer for over a decade, he returned to study for a Masters' degree in Information Technology and subsequently completed a PhD ("Internet-based Support for Creative Collaboration", 2007) in Computing Science.

Alastair has collaborated with artists on exhibited interactive artworks as well as publishing in the disciplines of HCI, Information Systems, Information Visualisation and Presence.

Visit profile

We've been working with the Django framework for a very long time, and we greatly appreciate the Django community and all the work that goes into the project.

Django 5.1 introduces the new querystring template tag. It's a really great addition to the Django template language, and it makes a lot of things very convenient. But for a long time we've been using our own update_GET tag and I wondered how the two tags compared.

What can you do with django-template-update-get?

The update_GET template tag allows you to output an updated version of the querystring of the current URL. As described in the README, you can use the update_GET template tag like this:

{% load template_update_get_tags %}
<a 
  href="{{ request.path }}?{% update_GET attr1 += value1 attr2 -= value2 attr3 = value3 %}"
>
  foo
</a>

This:

  • adds value1 to (the list of values in) attr1,
  • removes value2 from (the list of values in) attr2,
  • sets attr3 to value3.
  • and then returns a URL-encoded GET string.

Allowed values are:

  • vars that resolve to strings
  • strings, in quotes
  • lists of strings
  • None (without quotes)

If an attribute is set to None or an empty list, the GET parameter is removed.

We often use that for pagination or sorting of output results where the URL may already have GET parameters, for example:

{% load template_update_get_tags %} 
{% if request.GET.order == "year" %} 
  {# results are already ordered by year #} 
  {# so we want a link that would reverse the order #}
  <a 
    href="{{ request.path }}?{% update_GET 'order' = '-year' %}"
  >
      Sort by -Year 
  </a>
{% else %}
  <a 
    href="{{ request.path }}?{% update_GET 'order' = 'year' %}"
  >
    Sort by Year 
  </a>
{% endif %}

This will then work on a page like /search/?q=foo&tags=fish&page=2 and will output /search/?q=foo&tags=fish&page=2&order=year (or /search/?q=foo&tags=fish&page=2&order=-year if the order was already set to year).

What can you do with Django's querystring tag?

The new querystring tag is very similar and does many of the same things:

  • {% querystring order="title" %} adds a value to the querystring, so on the page /search/?q=foo it'll output ?q=foo&order=title.
  • {% querystring order=None %} removes a value from the querystirng, so on the page /search/?q=foo&order=title it'll output ?q=foo
  • {% querystring my_query_dict %}, if given a QueryDict instance in the context called my_query_dict will output that as a series of URL-encoded GET paramaters
  • {% querystring tags=tags_list %}, if given a list of strings in the context called tags_list will output them as a series of items like /search/?q=foo&tags=tag1&tags=tag2

How do they compare?

All very similar mainly, except the last one. We do use our update_GET template tag quite often for managing tags for filtering search results or lists, typically something like this:

In this case the URL of the page would be /search/?q=foo&tags=blue and, using our template tag, we'd be outputting something like this:

<a 
  href="{{ request.path }}?{% update_GET 'tags' = None %}"
>
  All ({{ results_count }})
</a>
{% for tag in all_tags %} 
  {% if tag.name in request.GET|getlist:"tags" %} 
    {# Tag is currently selected #}
    {# make a link that would unselect it #}
    <a 
      href="{{ request.path }}?{% update_GET 'tags' -= tag.name %}" 
      class="selected"
    >
      {{ tag.name|capfirst }} ({{ tag.count }})
    </a>
  {% else %}
    {# Tag is not selected #}
    {# make a link that would select it #}
    {# together with any other selected ones #}
    <a 
      href="{{ request.path }}?{% update_GET 'tags' += tag.name %}"
    >
      {{ tag.name|capfirst }} ({{ tag.count }})
    </a>
  {% endif %} 
{% endfor %}

and that would render as:

<a href="/search/?q=foo">All (500)</a>
<a href="/search/?q=foo&tags=blue&tags=red">Red (100)</a>
<a href="/search/?q=foo&tags=blue&tags=green">Green (200)</a>
<a href="/search/?q=foo" class="selected">Blue (300)</a>

So that means clicking on the All link removes any tag filters, clicking on a selected tag removes that tag filter but retains any others, and clicking on an unselected tag adds that tag filter, also retaining any others. It's a very neat way of managing tag filters in a search results page if you want to be able to select multiple tags at the same time.

Doing the same thing with the Django template tag is harder because you need to pass it the list of tags you want it to output in each case, so you'd have to manage that somehow as you go through the loop. I can't immediately think of a neat way of doing that, except by writing another template tag which would be a pain.

So, for the moment I'll be retaining my favourite template tag, at least for this particular use case. I'm now looking into how to propose a change to the Django project to extend the new querystring tag to allow for this, or at least to start the discussion about whether it's a common enough use case to be considered.

End of article.