+import re
+
+from django import template
+from django.utils.html import escape
+
+
+register = template.Library()
+
+
+class QueryStringNode(template.Node):
+ REPLACE = '='
+ APPEND = '+='
+ REMOVE = '-='
+ PURGE = '!'
+
+ OPERATORS = [REPLACE, APPEND, REMOVE, PURGE]
+
+ op_regex = re.compile('|'.join(map(re.escape, OPERATORS)))
+
+ def __init__(self, tag_name, parsed_args):
+ self.tag_name = tag_name
+ self.parsed_args = parsed_args
+
+ def render(self, context):
+ # django.core.context_processors.request should be enabled in
+ # settings.TEMPLATE_CONTEXT_PROCESSORS.
+ # Or else, directly pass the HttpRequest object as 'request' in context.
+
+ try:
+ query_dict = context['request'].GET.copy()
+ except KeyError:
+ return ''
+
+ for key, op, value in self.parsed_args:
+ key = key.resolve(context)
+
+ if op == self.APPEND:
+ # item+="foo": Append to current query arguments.
+ # e.g. item=1 -> item=1&item=foo
+ query_dict.appendlist(key, value.resolve(context))
+ elif op == self.REMOVE:
+ # item-="bar": Remove from current query arguments.
+ # e.g. item=1&item=bar -> item=1
+ try:
+ query_dict.getlist(key).remove(value.resolve(context))
+ except KeyError:
+ pass
+ elif op == self.PURGE:
+ # item!: Completely remove from current query arguments.
+ # e.g. item=1&item=2 -> ''
+ try:
+ del query_dict[key]
+ except KeyError:
+ pass
+ else:
+ # item=1: Replace current query arguments, e.g. item=2 -> item=1
+ query_dict[key] = value.resolve(context)
+
+ qs = query_dict.urlencode()
+ return '?' + escape(qs) if qs else ''
+
+
+@register.tag
+def query_string(parser, token):
+ # {% query_string page=1 size! item+="foo" item-="bar" %}
+ args = token.split_contents()
+
+ tag_name = args[0]
+ raw_pairs = args[1:]
+
+ parsed_args = []
+ for pair in raw_pairs:
+ try:
+ key, value = QueryStringNode.op_regex.split(pair, 1)
+ except:
+ raise template.TemplateSyntaxError("{} tag's argument should be in format foo({})bar".format(
+ tag_name, '|'.join(QueryStringNode.OPERATORS)))
+
+ parsed_args.append(
+ (parser.compile_filter(key), QueryStringNode.op_regex.search(pair).group(), parser.compile_filter(value)))
+
+ return QueryStringNode(tag_name, parsed_args)