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
|
Comments
This is awesome! Thank you so much!
#
This snippet is my favorite compared to the following list of other implementations I could find for a Django template tag which modifies the current HTTP GET query string.
On djangosnippets.org:
On Stack Overflow:
On PyPI:
On GitHub:
#
There's one piece missing from the code. After the imports this is needed:
#