/https://ic-web2-prd.s3.amazonaws.com/media/dd/images/a69b8deec02c5877c35b44e6f92c78be.png)
Is it time to retire my favourite template tag?
Written by
Dr Alastair Weakley
Published on 2 December 2024
:format(webp)/https://ic-web2-prd.s3.amazonaws.com/media/dd/images/5cf4e29d08447675aee18f569d01c5da.jpg)
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
value1
to (the list of values in)attr1
, - removes
value2
from (the list of values in)attr2
, - sets
attr3
tovalue3
. - 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 aQueryDict
instance in the context calledmy_query_dict
will output that as a series of URL-encodedGET
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.