Code

Ticket #12945: 12945.2.2.diff

File 12945.2.2.diff, 9.3 KB (added by SmileyChris, 4 years ago)
Line 
1diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
2index 0db77f1..3ffd071 100644
3--- a/django/template/defaulttags.py
4+++ b/django/template/defaulttags.py
5@@ -14,6 +14,8 @@ from django.utils.itercompat import groupby
6 from django.utils.safestring import mark_safe
7 
8 register = Library()
9+# Regex for token keyword arguments
10+kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
11 
12 class AutoEscapeControlNode(Node):
13     """Implements the actions of the autoescape tag."""
14@@ -1071,12 +1073,9 @@ def templatetag(parser, token):
15     return TemplateTagNode(tag)
16 templatetag = register.tag(templatetag)
17 
18-# Regex for URL arguments including filters
19-url_arg_re = re.compile(
20-    r"(?:(%(name)s)=)?(%(value)s(?:\|%(name)s(?::%(value)s)?)*)" % {
21-        'name':'\w+',
22-        'value':'''(?:(?:'[^']*')|(?:"[^"]*")|(?:[\w\.-]+))'''},
23-    re.VERBOSE)
24+# Backwards compatibility check which will fail against for old comma
25+# separated arguments in the url tag.
26+url_backwards_re = re.compile(r'''(('[^']*'|"[^"]*"|[^,]+)=?)+$''')
27 
28 def url(parser, token):
29     """
30@@ -1085,7 +1084,11 @@ def url(parser, token):
31     This is a way to define links that aren't tied to a particular URL
32     configuration::
33 
34-        {% url path.to.some_view arg1,arg2,name1=value1 %}
35+        {% url path.to.some_view arg1 arg2 %}
36+       
37+        or
38+       
39+        {% url path.to.some_view name1=value1 name2=value2 %}
40 
41     The first argument is a path to a view. It can be an absolute python path
42     or just ``app_name.view_name`` without the project name if the view is
43@@ -1117,27 +1120,28 @@ def url(parser, token):
44     args = []
45     kwargs = {}
46     asvar = None
47-
48-    if len(bits) > 2:
49-        bits = iter(bits[2:])
50+    bits = bits[2:]
51+    if len(bits) >= 2 and bits[-2] == 'as':
52+        asvar = bits[-1]
53+        bits = bits[:-2]
54+
55+    # Backwards compatibility: {% url urlname arg1,arg2 %} or
56+    # {% url urlname arg1,arg2 as foo %} cases.
57+    if bits:
58+        old_args = ''.join(bits)
59+        if not url_backwards_re.match(old_args):
60+            bits = old_args.split(",")
61+
62+    if len(bits):
63         for bit in bits:
64-            if bit == 'as':
65-                asvar = bits.next()
66-                break
67+            match = kwarg_re.match(bit)
68+            if not match:
69+                raise TemplateSyntaxError("Malformed arguments to url tag")
70+            name, value = match.groups()
71+            if name:
72+                kwargs[name] = parser.compile_filter(value)
73             else:
74-                end = 0
75-                for i, match in enumerate(url_arg_re.finditer(bit)):
76-                    if (i == 0 and match.start() != 0) or \
77-                          (i > 0 and (bit[end:match.start()] != ',')):
78-                        raise TemplateSyntaxError("Malformed arguments to url tag")
79-                    end = match.end()
80-                    name, value = match.group(1), match.group(2)
81-                    if name:
82-                        kwargs[name] = parser.compile_filter(value)
83-                    else:
84-                        args.append(parser.compile_filter(value))
85-                if end != len(bit):
86-                    raise TemplateSyntaxError("Malformed arguments to url tag")
87+                args.append(parser.compile_filter(value))
88 
89     return URLNode(viewname, args, kwargs, asvar)
90 url = register.tag(url)
91diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
92index aabacc5..f015370 100644
93--- a/docs/ref/templates/builtins.txt
94+++ b/docs/ref/templates/builtins.txt
95@@ -891,7 +891,7 @@ Returns an absolute URL (i.e., a URL without the domain name) matching a given
96 view function and optional parameters. This is a way to output links without
97 violating the DRY principle by having to hard-code URLs in your templates::
98 
99-    {% url path.to.some_view arg1,arg2,name1=value1 %}
100+    {% url path.to.some_view arg1 arg2 name1=value1 %}
101 
102 The first argument is a path to a view function in the format
103 ``package.package.module.function``. Additional arguments are optional and
104@@ -935,7 +935,7 @@ If you'd like to retrieve a URL without displaying it, you can use a slightly
105 different call::
106 
107 
108-    {% url path.to.view arg, arg2 as the_url %}
109+    {% url path.to.view arg arg2 as the_url %}
110 
111     <a href="{{ the_url }}">I'm linking to {{ the_url }}</a>
112 
113@@ -957,6 +957,12 @@ This will follow the normal :ref:`namespaced URL resolution strategy
114 <topics-http-reversing-url-namespaces>`, including using any hints provided
115 by the context as to the current application.
116 
117+.. versionchanged:: 1.2
118+
119+For backwards compatibility, the ``{% url %}`` tag supports the old syntax
120+from previous Django versions of comma separating the arguments. The previous
121+syntax does not support commas as part of string literals in the arguments.
122+
123 .. templatetag:: widthratio
124 
125 widthratio
126diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
127index 307fecc..0697fbb 100644
128--- a/tests/regressiontests/templates/tests.py
129+++ b/tests/regressiontests/templates/tests.py
130@@ -1020,9 +1020,15 @@ class Templates(unittest.TestCase):
131 
132             ### URL TAG ########################################################
133             # Successes
134+            'legacyurl02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
135+            'legacyurl02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
136+            'legacyurl10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
137+            '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/'),
138+            'legacyurl14': ('{% url regressiontests.templates.views.client_action client.id, arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
139+
140             'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
141-            'url02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
142-            'url02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
143+            'url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
144+            'url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
145             'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
146             'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
147             'url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
148@@ -1030,10 +1036,12 @@ class Templates(unittest.TestCase):
149             '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/'),
150             'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
151             'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
152-            'url10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
153-            'url11': ('{% url regressiontests.templates.views.client_action id=client.id,action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
154-            'url12': ('{% url regressiontests.templates.views.client_action id=client.id,action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
155-            '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/'),
156+            'url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
157+            'url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
158+            'url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
159+            '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/'),
160+            'url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
161+            'url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'),
162 
163             # Failures
164             'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),