Is it time to retire my favourite template tag?
Written by
Dr Alastair Weakley
Published on 2 December 2024
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.
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
value1to (the list of values in)attr1, - removes
value2from (the list of values in)attr2, - sets
attr3tovalue3. - and then returns a URL-encoded
GETstring.
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=fooit'll output?q=foo&order=title.{% querystring order=None %}removes a value from the querystirng, so on the page/search/?q=foo&order=titleit'll output?q=foo{% querystring my_query_dict %}, if given aQueryDictinstance in the context calledmy_query_dictwill output that as a series of URL-encodedGETparamaters{% 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.