This one works works with or without query string dicts defined in the context. And it handles replacement, addition and removal of values for parameters with multiple values.
Usage:
{% url view %}{% query_string qs tag+tags month=m %}
where view
, qs
(dict), tags
(list of strings) and m
(number)
are defined in the context. Full detail in the doc string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | import re
from django.template import Library, Node, TemplateSyntaxError
from django.http import QueryDict
from django.utils.encoding import smart_str
@register.tag
def query_string(parser, token):
"""
Template tag for creating and modifying query strings.
Syntax:
{% query_string [<base_querystring>] [modifier]* [as <var_name>] %}
modifier is <name><op><value> where op in {=, +, -}
Parameters:
- base_querystring: literal query string, e.g. '?tag=python&tag=django&year=2011',
or context variable bound to either
- a literal query string,
- a python dict with potentially lists as values, or
- a django QueryDict object
May be '' or None or missing altogether.
- modifiers may be repeated and have the form <name><op><value>.
They are processed in the order they appear.
name is taken as is for a parameter name.
op is one of {=, +, -}.
= replace all existing values of name with value(s)
+ add value(s) to existing values for name
- remove value(s) from existing values if present
value is either a literal parameter value
or a context variable. If it is a context variable
it may also be bound to a list.
- as <var name>: bind result to context variable instead of injecting in output
(same as in url tag).
Examples:
1. {% query_string '?tag=a&m=1&m=3&tag=b' tag+'c' m=2 tag-'b' as myqs %}
Result: myqs == '?m=2&tag=a&tag=c'
2. context = {'qs': {'tag': ['a', 'b'], 'year': 2011, 'month': 2},
'tags': ['c', 'd'],
'm': 4,}
{% query_string qs tag+tags month=m %}
Result: '?tag=a&tag=b&tag=c&tag=d&year=2011&month=4
"""
# matches 'tagname1+val1' or 'tagname1=val1' but not 'anyoldvalue'
mod_re = re.compile(r"^(\w+)(=|\+|-)(.*)$")
bits = token.split_contents()
qdict = None
mods = []
asvar = None
bits = bits[1:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
if len(bits) >= 1:
first = bits[0]
if not mod_re.match(first):
qdict = parser.compile_filter(first)
bits = bits[1:]
for bit in bits:
match = mod_re.match(bit)
if not match:
raise TemplateSyntaxError("Malformed arguments to query_string tag")
name, op, value = match.groups()
mods.append((name, op, parser.compile_filter(value)))
return QueryStringNode(qdict, mods, asvar)
class QueryStringNode(Node):
def __init__(self, qdict, mods, asvar):
self.qdict = qdict
self.mods = mods
self.asvar = asvar
def render(self, context):
mods = [(smart_str(k, 'ascii'), op, v.resolve(context))
for k, op, v in self.mods]
if self.qdict:
qdict = self.qdict.resolve(context)
else:
qdict = None
# Internally work only with QueryDict
qdict = self._get_initial_query_dict(qdict)
#assert isinstance(qdict, QueryDict)
for k, op, v in mods:
qdict.setlist(k, self._process_list(qdict.getlist(k), op, v))
qstring = qdict.urlencode()
if qstring:
qstring = '?' + qstring
if self.asvar:
context[self.asvar] = qstring
return ''
else:
return qstring
def _get_initial_query_dict(self, qdict):
if not qdict:
qdict = QueryDict(None, mutable=True)
elif isinstance(qdict, QueryDict):
qdict = qdict.copy()
elif isinstance(qdict, basestring):
if qdict.startswith('?'):
qdict = qdict[1:]
qdict = QueryDict(qdict, mutable=True)
else:
# Accept any old dict or list of pairs.
try:
pairs = qdict.items()
except:
pairs = qdict
qdict = QueryDict(None, mutable=True)
# Enter each pair into QueryDict object:
try:
for k, v in pairs:
# Convert values to unicode so that detecting
# membership works for numbers.
if isinstance(v, (list, tuple)):
for e in v:
qdict.appendlist(k,unicode(e))
else:
qdict.appendlist(k, unicode(v))
except:
# Wrong data structure, qdict remains empty.
pass
return qdict
def _process_list(self, current_list, op, val):
if not val:
if op == '=':
return []
else:
return current_list
# Deal with lists only.
if not isinstance(val, (list, tuple)):
val = [val]
val = [unicode(v) for v in val]
# Remove
if op == '-':
for v in val:
while v in current_list:
current_list.remove(v)
# Replace
elif op == '=':
current_list = val
# Add
elif op == '+':
for v in val:
current_list.append(v)
return current_list
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
This is awesome! Thank you so much!
#
There's one piece missing from the code. After the imports this is needed:
#
Please login first before commenting.