# Handle multiple query parameters gracefully in Django.

URL: https://okayrelax.com/blog/handle-multiple-query-parameters-gracefully-in-django/
Published: 2021-09-17
Modified: 2021-09-17

---

## Recommended reading.

- The [Custom template tags and filters](https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/) how-to on the official Django documentation.
- [Dealing With QueryString Parameters](https://simpleisbetterthancomplex.com/snippet/2016/08/22/dealing-with-querystring-parameters.html) authored by Vitor Freitas.
- Inspired by the post [Working with URLs in Python & Django](https://adriennedomingus.com/blog/working-with-urls-in-python-amp-django) authored by Adrienne Domingus. Not necessary for you to go through, just passing on some deserved credit. 🙂

## Foundation.

We have a model that has multiple, independent choice fields. In a non-admin view, We want to be able to:

- Filter by one parameter
- Filter by multiple parameters
- Retain the filters across pagination

While Vitor Freitas’ guide was helpful, the syntax was quite out-dated. So we did what we sometimes do - and find incredibly helpful - in case we want to integrate an internet snippet:

- Renamed variables so that they make sense to *me*, the consumer of a piece of code.
- Added some optional type hints to get all that IDE goodness: autocomplete suggestions and [docstrings](https://www.python.org/dev/peps/pep-0257/) for classes, methods, and functions.
- Added a bunch of inline comments - never forget comments! They’re so helpful… to you today, to you a few months from now, and to anyone else at any point in time!
- We also made one small change in the order of the arguments, as it makes more sense to us this way: swap value and key being passed. Now they read as, “What query key do I want to set, and what value do I want to set on it?” which feels way more natural to us.
- As a bonus, we also opted to use [QueryDict](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.QueryDict) across the function definition as inspired from Adrienne Domingus. This is in addition to receiving the request.GET QueryDict directly, instead of its string representation.

## My snippet.

Note that we opted for a small duplication/repetition as it favors readability: return f"?{new_querydict.urlencode()}".

# {app_label}/templatetags/query_helpers.py

from django import template
from django.http import QueryDict

register = template.Library()

@register.simple_tag
def relative_url(
param_key: str,
param_value: str,
querydict: QueryDict = None
) -> str:
"""
Construct and return a query params string.
Arguments passed to this function take
precedence over any existing query param
for a request.
"""
# QueryDicts are immutable by default.
new_querydict = QueryDict(mutable=True)
new_querydict.appendlist(param_key, param_value)

if not querydict:
# No existing query params in the URL, so we're done.
# We just needed to append the args that were
# passed to this function.
return f"?{new_querydict.urlencode()}"
else:
# This means there are some existing params in the URL.
# We'd like to append them now and return that URL.
# At the same time, we want to ensure that the
# param_key and param_value passed to the function
# take precedence over the one in the URL.
# That happens when we check for key != param_key.
for key, value in querydict.items():
if key != param_key:
new_querydict.appendlist(key, value)
return f"?{new_querydict.urlencode()}"

### Sample usage.

# templates/{app_label}/{model_name}_list.html

{% load query_helpers %}

{% comment %} A simple pagination for demonstration {% endcomment %}
<nav>
<a href="{% query_helpers 'page' 1 request.GET %}"></a>
<a href="{% query_helpers 'page' 2 request.GET %}"></a>
</nav>

{% comment %} Some other simple filter for demonstration {% endcomment %}
<section>
<a href="{% query_helpers 'priority' 'HIGH' request.GET %}"></a>
<a href="{% query_helpers 'priority' 'LOW' request.GET %}"></a>
</section>
