The problem
Sometimes you want to add and remove URL parameters without knowing what’s already there. Say you are currently generating this URL:
/search/?q=foo&filter=bar&page=2
and, in that page, you have a button called Remove filters that should link to:
/search/?q=foo&page=2
The solution
This Django snippet implements a template tag called query_string that works like this:
{% query_string '' '' %}
where the first argument contains URL parameters that you want to add or modify, and the second one includes URL parameters that you want to remove.
Examples
<a href="{% query_string '' 'filter' %}"> Remove filters </a> <a href="{% query_string 'page=3' '' %}"> Go to page 3 </a>
The problem with this approach
While this approach has worked great for me for a while, I eventually hit a stumbling block: if you have a form for a group of checkboxes, and are submitting said form via GET, you will end up with an URL that looks like:
/search/?q=foo&opts=1&opts=2&opts=3
See how the opts
parameter repeats several times? That’s a design flaw if you
ask me, but that’s how HTTP seems to work.
When the URL parameters are pulled out of Django’s QueryDict
, only the last
one is preserved, i.e. opts=3
, so you’d lose the tick on some checkboxes.
To prevent that, I have modified the query_string
snippet. Here’s a version
that works with MultipleChoiceFields
, and multiple occurrences of the same
URL parameter in general (I have replaced only the functions I modified. Please
refer to the original snippet for the rest.)
class QueryStringNode(Node): def __init__(self, add,remove): self.add = add self.remove = remove def render(self, context): p_list = [] p_dict = {} query = context["request"].GET for k in query: p_list.append([k, query.getlist(k)]) p_dict[k] = query.getlist(k) return get_query_string( p_list, p_dict, self.add, self.remove, context) def get_query_string(p_list, p_dict, new_params, remove, context): """ Add and remove query parameters. From `django.contrib.admin`. """ for r in remove: p_list = [[x[0], x[1]]\ for x in p_list if not x[0].startswith(r)] for k, v in new_params.items(): if k in p_dict and v is None: p_list = [[x[0], x[1]]\ for x in p_list if not x[0] == k] elif k in p_dict and v is not None: for i in range(0, len(p_list)): if p_list[i][0] == k: p_list[i][1] = [v] elif v is not None: p_list.append([k, [v]]) for i in range(0, len(p_list)): if len(p_list[i][1]) == 1: p_list[i][1] = p_list[i][1][0] else: p_list[i][1] = mark_safe( '&'.join( [u'%s=%s' % (p_list[i][0], k) for k in p_list[i][1]])) p_list[i][0] = '' try: p_list[i][1] = template.Variable( p_list[i][1]).resolve(context) except: pass return mark_safe( '?' + '&'.join( [k[1] if k[0] == '' else u'%s=%s' % (k[0], k[1]) for k in p_list]).replace(' ', '%20'))
Hope this helps.