Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #12945 -- Corrected the parsing of arguments in {% url %} when …

…the argument list has spaces between commas. This is a revised version of r12503, which was a fix for #12072. Thanks to SmileyChris for the patch, and to dmoisset for finding all the places in the docs that the old style syntax was used.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12889 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit dafc077e4a8d12defb3adc6c69c131f31700ba23 1 parent 273a002
Russell Keith-Magee authored March 30, 2010
2  django/contrib/admin/templates/registration/password_reset_email.html
@@ -4,7 +4,7 @@
4 4
 
5 5
 {% trans "Please go to the following page and choose a new password:" %}
6 6
 {% block reset_link %}
7  
-{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %}
  7
+{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid token=token %}
8 8
 {% endblock %}
9 9
 {% trans "Your username, in case you've forgotten:" %} {{ user.username }}
10 10
 
56  django/template/defaulttags.py
@@ -14,6 +14,8 @@
14 14
 from django.utils.safestring import mark_safe
15 15
 
16 16
 register = Library()
  17
+# Regex for token keyword arguments
  18
+kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
17 19
 
18 20
 class AutoEscapeControlNode(Node):
19 21
     """Implements the actions of the autoescape tag."""
@@ -1063,12 +1065,9 @@ def templatetag(parser, token):
1063 1065
     return TemplateTagNode(tag)
1064 1066
 templatetag = register.tag(templatetag)
1065 1067
 
1066  
-# Regex for URL arguments including filters
1067  
-url_arg_re = re.compile(
1068  
-    r"(?:(%(name)s)=)?(%(value)s(?:\|%(name)s(?::%(value)s)?)*)" % {
1069  
-        'name':'\w+',
1070  
-        'value':'''(?:(?:'[^']*')|(?:"[^"]*")|(?:[\w\.-]+))'''},
1071  
-    re.VERBOSE)
  1068
+# Backwards compatibility check which will fail against for old comma
  1069
+# separated arguments in the url tag.
  1070
+url_backwards_re = re.compile(r'''(('[^']*'|"[^"]*"|[^,]+)=?)+$''')
1072 1071
 
1073 1072
 def url(parser, token):
1074 1073
     """
@@ -1077,7 +1076,11 @@ def url(parser, token):
1077 1076
     This is a way to define links that aren't tied to a particular URL
1078 1077
     configuration::
1079 1078
 
1080  
-        {% url path.to.some_view arg1,arg2,name1=value1 %}
  1079
+        {% url path.to.some_view arg1 arg2 %}
  1080
+
  1081
+        or
  1082
+
  1083
+        {% url path.to.some_view name1=value1 name2=value2 %}
1081 1084
 
1082 1085
     The first argument is a path to a view. It can be an absolute python path
1083 1086
     or just ``app_name.view_name`` without the project name if the view is
@@ -1109,27 +1112,28 @@ def url(parser, token):
1109 1112
     args = []
1110 1113
     kwargs = {}
1111 1114
     asvar = None
1112  
-
1113  
-    if len(bits) > 2:
1114  
-        bits = iter(bits[2:])
  1115
+    bits = bits[2:]
  1116
+    if len(bits) >= 2 and bits[-2] == 'as':
  1117
+        asvar = bits[-1]
  1118
+        bits = bits[:-2]
  1119
+
  1120
+    # Backwards compatibility: {% url urlname arg1,arg2 %} or
  1121
+    # {% url urlname arg1,arg2 as foo %} cases.
  1122
+    if bits:
  1123
+        old_args = ''.join(bits)
  1124
+        if not url_backwards_re.match(old_args):
  1125
+            bits = old_args.split(",")
  1126
+
  1127
+    if len(bits):
1115 1128
         for bit in bits:
1116  
-            if bit == 'as':
1117  
-                asvar = bits.next()
1118  
-                break
  1129
+            match = kwarg_re.match(bit)
  1130
+            if not match:
  1131
+                raise TemplateSyntaxError("Malformed arguments to url tag")
  1132
+            name, value = match.groups()
  1133
+            if name:
  1134
+                kwargs[name] = parser.compile_filter(value)
1119 1135
             else:
1120  
-                end = 0
1121  
-                for i, match in enumerate(url_arg_re.finditer(bit)):
1122  
-                    if (i == 0 and match.start() != 0) or \
1123  
-                          (i > 0 and (bit[end:match.start()] != ',')):
1124  
-                        raise TemplateSyntaxError("Malformed arguments to url tag")
1125  
-                    end = match.end()
1126  
-                    name, value = match.group(1), match.group(2)
1127  
-                    if name:
1128  
-                        kwargs[name] = parser.compile_filter(value)
1129  
-                    else:
1130  
-                        args.append(parser.compile_filter(value))
1131  
-                if end != len(bit):
1132  
-                    raise TemplateSyntaxError("Malformed arguments to url tag")
  1136
+                args.append(parser.compile_filter(value))
1133 1137
 
1134 1138
     return URLNode(viewname, args, kwargs, asvar)
1135 1139
 url = register.tag(url)
20  docs/ref/templates/builtins.txt
@@ -904,7 +904,7 @@ Returns an absolute URL (i.e., a URL without the domain name) matching a given
904 904
 view function and optional parameters. This is a way to output links without
905 905
 violating the DRY principle by having to hard-code URLs in your templates::
906 906
 
907  
-    {% url path.to.some_view v1,v2 %}
  907
+    {% url path.to.some_view v1 v2 %}
908 908
 
909 909
 The first argument is a path to a view function in the format
910 910
 ``package.package.module.function``. Additional arguments are optional and
@@ -912,7 +912,7 @@ should be comma-separated values that will be used as arguments in the URL.
912 912
 The example above shows passing positional arguments. Alternatively you may
913 913
 use keyword syntax::
914 914
 
915  
-    {% url path.to.some_view arg1=v1,arg2=v2 %}
  915
+    {% url path.to.some_view arg1=v1 arg2=v2 %}
916 916
 
917 917
 Do not mix both positional and keyword syntax in a single call. All arguments
918 918
 required by the URLconf should be present.
@@ -954,7 +954,7 @@ If you'd like to retrieve a URL without displaying it, you can use a slightly
954 954
 different call::
955 955
 
956 956
 
957  
-    {% url path.to.view arg, arg2 as the_url %}
  957
+    {% url path.to.view arg arg2 as the_url %}
958 958
 
959 959
     <a href="{{ the_url }}">I'm linking to {{ the_url }}</a>
960 960
 
@@ -976,6 +976,20 @@ This will follow the normal :ref:`namespaced URL resolution strategy
976 976
 <topics-http-reversing-url-namespaces>`, including using any hints provided
977 977
 by the context as to the current application.
978 978
 
  979
+.. versionchanged:: 1.2
  980
+
  981
+For backwards compatibility, the ``{% url %}`` tag also supports the
  982
+use of commas to separate arguments. You shouldn't use this in any new
  983
+projects, but for the sake of the people who are still using it,
  984
+here's what it looks like::
  985
+
  986
+    {% url path.to.view arg,arg2 %}
  987
+    {% url path.to.view arg, arg2 %}
  988
+
  989
+This syntax doesn't support the use of literal commas, or or equals
  990
+signs. Did we mention you shouldn't use this syntax in any new
  991
+projects?
  992
+
979 993
 .. templatetag:: widthratio
980 994
 
981 995
 widthratio
32  tests/regressiontests/templates/tests.py
@@ -205,20 +205,20 @@ def test_loader_debug_origin(self):
205 205
 
206 206
     def test_extends_include_missing_baseloader(self):
207 207
         """
208  
-        Tests that the correct template is identified as not existing 
209  
-        when {% extends %} specifies a template that does exist, but 
  208
+        Tests that the correct template is identified as not existing
  209
+        when {% extends %} specifies a template that does exist, but
210 210
         that template has an {% include %} of something that does not
211 211
         exist. See #12787.
212 212
         """
213 213
 
214  
-        # TEMPLATE_DEBUG must be true, otherwise the exception raised 
  214
+        # TEMPLATE_DEBUG must be true, otherwise the exception raised
215 215
         # during {% include %} processing will be suppressed.
216 216
         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True
217 217
         old_loaders = loader.template_source_loaders
218 218
 
219 219
         try:
220  
-            # Test the base loader class via the app loader. load_template 
221  
-            # from base is used by all shipped loaders excepting cached, 
  220
+            # Test the base loader class via the app loader. load_template
  221
+            # from base is used by all shipped loaders excepting cached,
222 222
             # which has its own test.
223 223
             loader.template_source_loaders = (app_directories.Loader(),)
224 224
 
@@ -237,7 +237,7 @@ def test_extends_include_missing_baseloader(self):
237 237
 
238 238
     def test_extends_include_missing_cachedloader(self):
239 239
         """
240  
-        Same as test_extends_include_missing_baseloader, only tests 
  240
+        Same as test_extends_include_missing_baseloader, only tests
241 241
         behavior of the cached loader instead of BaseLoader.
242 242
         """
243 243
 
@@ -1173,9 +1173,15 @@ def get_template_tests(self):
1173 1173
 
1174 1174
             ### URL TAG ########################################################
1175 1175
             # Successes
  1176
+            'legacyurl02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
  1177
+            'legacyurl02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
  1178
+            'legacyurl10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
  1179
+            'legacyurl13': ('{% url regressiontests.templates.views.client_action id=client.id, action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
  1180
+            'legacyurl14': ('{% url regressiontests.templates.views.client_action client.id, arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
  1181
+
1176 1182
             'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
1177  
-            'url02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
1178  
-            'url02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
  1183
+            'url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
  1184
+            'url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
1179 1185
             'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
1180 1186
             'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
1181 1187
             'url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
@@ -1183,10 +1189,12 @@ def get_template_tests(self):
1183 1189
             'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
1184 1190
             'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
1185 1191
             'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
1186  
-            'url10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
1187  
-            'url11': ('{% url regressiontests.templates.views.client_action id=client.id,action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
1188  
-            'url12': ('{% url regressiontests.templates.views.client_action id=client.id,action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
1189  
-            'url12': ('{% url regressiontests.templates.views.client_action id=client.id,action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
  1192
+            'url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
  1193
+            'url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
  1194
+            'url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
  1195
+            'url13': ('{% url regressiontests.templates.views.client_action id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
  1196
+            'url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
  1197
+            'url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'),
1190 1198
 
1191 1199
             # Failures
1192 1200
             'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),

0 notes on commit dafc077

Please sign in to comment.
Something went wrong with that request. Please try again.