Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Use `token.split_contents()` in tags that can take variables (fixes #6271 and #18260) #751

Merged
merged 1 commit into from over 1 year ago

3 participants

Baptiste Mispelon Aymeric Augustin Curtis Maloney
Baptiste Mispelon
Owner

After fixing #19882, I grepped through the code to check for usage of token.contents.split().

It turned out that several other tags were affected by the same issue, so I fix those.

There are still tags that use token.contents.split() instead of token.split_contents(), but since they don't take any variable argument, they are not affected.

Here's a list of the remaining instances of token.contents.split() and the tags they are associated with:

django/template/base.py:262: <not a template tag>
django/template/defaulttags.py:492: autoescape
django/template/defaulttags.py:636: filter
django/template/defaulttags.py:1014: load
django/template/defaulttags.py:1186: templatetag
django/template/loader_tags.py:178: block
django/templatetags/i18n.py:188: get_available_languages
django/templatetags/i18n.py:260: get_current_language
django/templatetags/i18n.py:278: get_current_language_bidi
django/templatetags/static.py:30: get_static_prefix, get_media_prefix
django/templatetags/tz.py:193: get_current_timezone

Also note that there are still tags that are affected by this issue in contrib.
Namely, all the tags from contrib.comments as well as the get_admin_log for contrib.admin.
Those did not have any tests and I didn't see any bugs in the tracker, so I left them as-is (for now).

Aymeric Augustin
Owner

I've wanted to write that patch forever...

Aymeric Augustin
Owner

Would you mind:

  • adding a short comment above each use of .content.split that you didn't change?

    # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments

  • rebasing this to a single commit with the following commit message ?

    Used token.split_contents() for tokenisation in template tags accepting variables.

    Fixed #6271, #18260.


Then I'll run the tests and merge it.

Baptiste Mispelon
Owner

Done.

As discussed on IRC, I'll file a separate ticket for the similar issues in contrib.comments and contrib.admin.

Aymeric Augustin aaugustin merged commit 5278776 into from
Curtis Maloney

Here you parse_filter the fragment_name, but it's never resolved above. It only works because the FilterExpression.str method returns self.token

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Feb 23, 2013
Baptiste Mispelon bmispelon Used token.split_contents() for tokenisation in template tags accepti…
…ng variables.

Fixed #6271, #18260.
069280a
This page is out of date. Refresh to see the latest.
23 django/template/defaulttags.py
@@ -489,6 +489,7 @@ def autoescape(parser, token):
489 489 """
490 490 Force autoescape behavior for this block.
491 491 """
  492 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
492 493 args = token.contents.split()
493 494 if len(args) != 2:
494 495 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
@@ -633,6 +634,7 @@ def do_filter(parser, token):
633 634 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of
634 635 template code.
635 636 """
  637 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
636 638 _, rest = token.contents.split(None, 1)
637 639 filter_expr = parser.compile_filter("var|%s" % (rest))
638 640 for func, unused in filter_expr.filters:
@@ -954,7 +956,7 @@ def ifchanged(parser, token):
954 956 {% endifchanged %}
955 957 {% endfor %}
956 958 """
957   - bits = token.contents.split()
  959 + bits = token.split_contents()
958 960 nodelist_true = parser.parse(('else', 'endifchanged'))
959 961 token = parser.next_token()
960 962 if token.contents == 'else':
@@ -1011,6 +1013,7 @@ def load(parser, token):
1011 1013 {% load byline from news %}
1012 1014
1013 1015 """
  1016 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
1014 1017 bits = token.contents.split()
1015 1018 if len(bits) >= 4 and bits[-2] == "from":
1016 1019 try:
@@ -1109,17 +1112,16 @@ def regroup(parser, token):
1109 1112 {% regroup people|dictsort:"gender" by gender as grouped %}
1110 1113
1111 1114 """
1112   - firstbits = token.contents.split(None, 3)
1113   - if len(firstbits) != 4:
  1115 + bits = token.split_contents()
  1116 + if len(bits) != 6:
1114 1117 raise TemplateSyntaxError("'regroup' tag takes five arguments")
1115   - target = parser.compile_filter(firstbits[1])
1116   - if firstbits[2] != 'by':
  1118 + target = parser.compile_filter(bits[1])
  1119 + if bits[2] != 'by':
1117 1120 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
1118   - lastbits_reversed = firstbits[3][::-1].split(None, 2)
1119   - if lastbits_reversed[1][::-1] != 'as':
  1121 + if bits[4] != 'as':
1120 1122 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
1121 1123 " be 'as'")
1122   - var_name = lastbits_reversed[0][::-1]
  1124 + var_name = bits[5]
1123 1125 # RegroupNode will take each item in 'target', put it in the context under
1124 1126 # 'var_name', evaluate 'var_name'.'expression' in the current context, and
1125 1127 # group by the resulting value. After all items are processed, it will
@@ -1128,7 +1130,7 @@ def regroup(parser, token):
1128 1130 # doesn't provide a context-aware equivalent of Python's getattr.
1129 1131 expression = parser.compile_filter(var_name +
1130 1132 VARIABLE_ATTRIBUTE_SEPARATOR +
1131   - lastbits_reversed[2][::-1])
  1133 + bits[3])
1132 1134 return RegroupNode(target, expression, var_name)
1133 1135
1134 1136 @register.tag
@@ -1184,6 +1186,7 @@ def templatetag(parser, token):
1184 1186 ``closecomment`` ``#}``
1185 1187 ================== =======
1186 1188 """
  1189 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
1187 1190 bits = token.contents.split()
1188 1191 if len(bits) != 2:
1189 1192 raise TemplateSyntaxError("'templatetag' statement takes one argument")
@@ -1325,7 +1328,7 @@ def widthratio(parser, token):
1325 1328 the image in the above example will be 88 pixels wide
1326 1329 (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
1327 1330 """
1328   - bits = token.contents.split()
  1331 + bits = token.split_contents()
1329 1332 if len(bits) != 4:
1330 1333 raise TemplateSyntaxError("widthratio takes three arguments")
1331 1334 tag, this_value_expr, max_value_expr, max_width = bits
1  django/template/loader_tags.py
@@ -174,6 +174,7 @@ def do_block(parser, token):
174 174 """
175 175 Define a block that can be overridden by child templates.
176 176 """
  177 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
177 178 bits = token.contents.split()
178 179 if len(bits) != 2:
179 180 raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
14 django/templatetags/cache.py
... ... @@ -1,8 +1,7 @@
1 1 from __future__ import unicode_literals
2 2
3 3 import hashlib
4   -from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
5   -from django.template import resolve_variable
  4 +from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist
6 5 from django.core.cache import cache
7 6 from django.utils.encoding import force_bytes
8 7 from django.utils.http import urlquote
@@ -12,7 +11,7 @@
12 11 class CacheNode(Node):
13 12 def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
14 13 self.nodelist = nodelist
15   - self.expire_time_var = Variable(expire_time_var)
  14 + self.expire_time_var = expire_time_var
16 15 self.fragment_name = fragment_name
17 16 self.vary_on = vary_on
18 17
@@ -26,7 +25,7 @@ def render(self, context):
26 25 except (ValueError, TypeError):
27 26 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
28 27 # Build a key for this fragment and all vary-on's.
29   - key = ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])
  28 + key = ':'.join([urlquote(var.resolve(context)) for var in self.vary_on])
30 29 args = hashlib.md5(force_bytes(key))
31 30 cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
32 31 value = cache.get(cache_key)
@@ -59,7 +58,10 @@ def do_cache(parser, token):
59 58 """
60 59 nodelist = parser.parse(('endcache',))
61 60 parser.delete_first_token()
62   - tokens = token.contents.split()
  61 + tokens = token.split_contents()
63 62 if len(tokens) < 3:
64 63 raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0])
65   - return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
  64 + return CacheNode(nodelist,
  65 + parser.compile_filter(tokens[1]),
  66 + parser.compile_filter(tokens[2]),
  67 + [parser.compile_filter(token) for token in tokens[3:]])
15 django/templatetags/i18n.py
@@ -24,7 +24,7 @@ def render(self, context):
24 24
25 25 class GetLanguageInfoNode(Node):
26 26 def __init__(self, lang_code, variable):
27   - self.lang_code = Variable(lang_code)
  27 + self.lang_code = lang_code
28 28 self.variable = variable
29 29
30 30 def render(self, context):
@@ -35,7 +35,7 @@ def render(self, context):
35 35
36 36 class GetLanguageInfoListNode(Node):
37 37 def __init__(self, languages, variable):
38   - self.languages = Variable(languages)
  38 + self.languages = languages
39 39 self.variable = variable
40 40
41 41 def get_language_info(self, language):
@@ -185,6 +185,7 @@ def do_get_available_languages(parser, token):
185 185 your setting file (or the default settings) and
186 186 put it into the named variable.
187 187 """
  188 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
188 189 args = token.contents.split()
189 190 if len(args) != 3 or args[1] != 'as':
190 191 raise TemplateSyntaxError("'get_available_languages' requires 'as variable' (got %r)" % args)
@@ -204,10 +205,10 @@ def do_get_language_info(parser, token):
204 205 {{ l.name_local }}
205 206 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
206 207 """
207   - args = token.contents.split()
  208 + args = token.split_contents()
208 209 if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
209 210 raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:]))
210   - return GetLanguageInfoNode(args[2], args[4])
  211 + return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])
211 212
212 213 @register.tag("get_language_info_list")
213 214 def do_get_language_info_list(parser, token):
@@ -227,10 +228,10 @@ def do_get_language_info_list(parser, token):
227 228 {{ l.bidi|yesno:"bi-directional,uni-directional" }}
228 229 {% endfor %}
229 230 """
230   - args = token.contents.split()
  231 + args = token.split_contents()
231 232 if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
232 233 raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:]))
233   - return GetLanguageInfoListNode(args[2], args[4])
  234 + return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4])
234 235
235 236 @register.filter
236 237 def language_name(lang_code):
@@ -257,6 +258,7 @@ def do_get_current_language(parser, token):
257 258 put it's value into the ``language`` context
258 259 variable.
259 260 """
  261 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
260 262 args = token.contents.split()
261 263 if len(args) != 3 or args[1] != 'as':
262 264 raise TemplateSyntaxError("'get_current_language' requires 'as variable' (got %r)" % args)
@@ -275,6 +277,7 @@ def do_get_current_language_bidi(parser, token):
275 277 put it's value into the ``bidi`` context variable.
276 278 True indicates right-to-left layout, otherwise left-to-right
277 279 """
  280 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
278 281 args = token.contents.split()
279 282 if len(args) != 3 or args[1] != 'as':
280 283 raise TemplateSyntaxError("'get_current_language_bidi' requires 'as variable' (got %r)" % args)
1  django/templatetags/static.py
@@ -27,6 +27,7 @@ def handle_token(cls, parser, token, name):
27 27 """
28 28 Class method to parse prefix node and return a Node.
29 29 """
  30 + # token.split_contents() isn't useful here because tags using this method don't accept variable as arguments
30 31 tokens = token.contents.split()
31 32 if len(tokens) > 1 and tokens[1] != 'as':
32 33 raise template.TemplateSyntaxError(
1  django/templatetags/tz.py
@@ -190,6 +190,7 @@ def get_current_timezone_tag(parser, token):
190 190 This will fetch the currently active time zone and put its name
191 191 into the ``TIME_ZONE`` context variable.
192 192 """
  193 + # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
193 194 args = token.contents.split()
194 195 if len(args) != 3 or args[1] != 'as':
195 196 raise TemplateSyntaxError("'get_current_timezone' requires "
7 tests/regressiontests/templates/templatetags/custom.py
@@ -12,6 +12,13 @@
12 12 def trim(value, num):
13 13 return value[:num]
14 14
  15 +@register.filter
  16 +def noop(value, param=None):
  17 + """A noop filter that always return its first argument and does nothing with
  18 + its second (optional) one.
  19 + Useful for testing out whitespace in filter arguments (see #19882)."""
  20 + return value
  21 +
15 22 @register.simple_tag
16 23 def no_params():
17 24 """Expected no_params __doc__"""
25 tests/regressiontests/templates/tests.py
@@ -834,7 +834,7 @@ def get_template_tests(self):
834 834 'for-tag-empty02': ("{% for val in values %}{{ val }}{% empty %}values array empty{% endfor %}", {"values": []}, "values array empty"),
835 835 'for-tag-empty03': ("{% for val in values %}{{ val }}{% empty %}values array not found{% endfor %}", {}, "values array not found"),
836 836 # Ticket 19882
837   - 'for-tag-filter-ws': ("{% for x in ''|add:'a b c' %}{{ x }}{% endfor %}", {}, 'a b c'),
  837 + 'for-tag-filter-ws': ("{% load custom %}{% for x in s|noop:'x y' %}{{ x }}{% endfor %}", {'s': 'abc'}, 'abc'),
838 838
839 839 ### IF TAG ################################################################
840 840 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
@@ -1003,6 +1003,9 @@ def get_template_tests(self):
1003 1003
1004 1004 'ifchanged-else04': ('{% for id in ids %}{% ifchanged %}***{{ id }}*{% else %}...{% endifchanged %}{{ forloop.counter }}{% endfor %}', {'ids': [1,1,2,2,2,3,4]}, '***1*1...2***2*3...4...5***3*6***4*7'),
1005 1005
  1006 + # Test whitespace in filter arguments
  1007 + 'ifchanged-filter-ws': ('{% load custom %}{% for n in num %}{% ifchanged n|noop:"x y" %}..{% endifchanged %}{{ n }}{% endfor %}', {'num': (1,2,3)}, '..1..2..3'),
  1008 +
1006 1009 ### IFEQUAL TAG ###########################################################
1007 1010 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
1008 1011 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
@@ -1343,6 +1346,10 @@ def get_template_tests(self):
1343 1346 'i18n36': ('{% load i18n %}{% trans "Page not found" as page_not_found noop %}{{ page_not_found }}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
1344 1347 'i18n37': ('{% load i18n %}{% trans "Page not found" as page_not_found %}{% blocktrans %}Error: {{ page_not_found }}{% endblocktrans %}', {'LANGUAGE_CODE': 'de'}, "Error: Seite nicht gefunden"),
1345 1348
  1349 + # Test whitespace in filter arguments
  1350 + 'i18n38': ('{% load i18n custom %}{% get_language_info for "de"|noop:"x y" as l %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'),
  1351 + 'i18n38_2': ('{% load i18n custom %}{% get_language_info_list for langcodes|noop:"x y" as langs %}{% for l in langs %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, 'it: Italian/italiano bidi=False; no: Norwegian/norsk bidi=False; '),
  1352 +
1346 1353 ### HANDLING OF TEMPLATE_STRING_IF_INVALID ###################################
1347 1354
1348 1355 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
@@ -1424,6 +1431,16 @@ def get_template_tests(self):
1424 1431 {'foo': 'z', 'bar': ['a', 'd']}]},
1425 1432 'abc:xy,ad:z,'),
1426 1433
  1434 + # Test syntax
  1435 + 'regroup05': ('{% regroup data by bar as %}', {},
  1436 + template.TemplateSyntaxError),
  1437 + 'regroup06': ('{% regroup data by bar thisaintright grouped %}', {},
  1438 + template.TemplateSyntaxError),
  1439 + 'regroup07': ('{% regroup data thisaintright bar as grouped %}', {},
  1440 + template.TemplateSyntaxError),
  1441 + 'regroup08': ('{% regroup data by bar as grouped toomanyargs %}', {},
  1442 + template.TemplateSyntaxError),
  1443 +
1427 1444 ### SSI TAG ########################################################
1428 1445
1429 1446 # Test normal behavior
@@ -1492,6 +1509,9 @@ def get_template_tests(self):
1492 1509 'widthratio14a': ('{% widthratio a b c %}', {'a':0,'b':100,'c':'c'}, template.TemplateSyntaxError),
1493 1510 'widthratio14b': ('{% widthratio a b c %}', {'a':0,'b':100,'c':None}, template.TemplateSyntaxError),
1494 1511
  1512 + # Test whitespace in filter argument
  1513 + 'widthratio15': ('{% load custom %}{% widthratio a|noop:"x y" b 0 %}', {'a':50,'b':100}, '0'),
  1514 +
1495 1515 ### WITH TAG ########################################################
1496 1516 'with01': ('{% with key=dict.key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'),
1497 1517 'legacywith01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'),
@@ -1593,6 +1613,9 @@ def get_template_tests(self):
1593 1613 # Regression test for #11270.
1594 1614 'cache17': ('{% load cache %}{% cache 10 long_cache_key poem %}Some Content{% endcache %}', {'poem': 'Oh freddled gruntbuggly/Thy micturations are to me/As plurdled gabbleblotchits/On a lurgid bee/That mordiously hath bitled out/Its earted jurtles/Into a rancid festering/Or else I shall rend thee in the gobberwarts with my blurglecruncheon/See if I dont.'}, 'Some Content'),
1595 1615
  1616 + # Test whitespace in filter arguments
  1617 + 'cache18': ('{% load cache custom %}{% cache 2|noop:"x y" cache18 %}cache18{% endcache %}', {}, 'cache18'),
  1618 +
1596 1619
1597 1620 ### AUTOESCAPE TAG ##############################################
1598 1621 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.