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
This commit is contained in:
Russell Keith-Magee 2010-03-30 12:44:30 +00:00
parent 273a002544
commit dafc077e4a
4 changed files with 67 additions and 41 deletions

View File

@ -4,7 +4,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %}
{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}

View File

@ -14,6 +14,8 @@ from django.utils.itercompat import groupby
from django.utils.safestring import mark_safe
register = Library()
# Regex for token keyword arguments
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
class AutoEscapeControlNode(Node):
"""Implements the actions of the autoescape tag."""
@ -1063,12 +1065,9 @@ def templatetag(parser, token):
return TemplateTagNode(tag)
templatetag = register.tag(templatetag)
# Regex for URL arguments including filters
url_arg_re = re.compile(
r"(?:(%(name)s)=)?(%(value)s(?:\|%(name)s(?::%(value)s)?)*)" % {
'name':'\w+',
'value':'''(?:(?:'[^']*')|(?:"[^"]*")|(?:[\w\.-]+))'''},
re.VERBOSE)
# Backwards compatibility check which will fail against for old comma
# separated arguments in the url tag.
url_backwards_re = re.compile(r'''(('[^']*'|"[^"]*"|[^,]+)=?)+$''')
def url(parser, token):
"""
@ -1077,7 +1076,11 @@ def url(parser, token):
This is a way to define links that aren't tied to a particular URL
configuration::
{% url path.to.some_view arg1,arg2,name1=value1 %}
{% url path.to.some_view arg1 arg2 %}
or
{% url path.to.some_view name1=value1 name2=value2 %}
The first argument is a path to a view. It can be an absolute python path
or just ``app_name.view_name`` without the project name if the view is
@ -1109,27 +1112,28 @@ def url(parser, token):
args = []
kwargs = {}
asvar = None
bits = bits[2:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
if len(bits) > 2:
bits = iter(bits[2:])
# Backwards compatibility: {% url urlname arg1,arg2 %} or
# {% url urlname arg1,arg2 as foo %} cases.
if bits:
old_args = ''.join(bits)
if not url_backwards_re.match(old_args):
bits = old_args.split(",")
if len(bits):
for bit in bits:
if bit == 'as':
asvar = bits.next()
break
match = kwarg_re.match(bit)
if not match:
raise TemplateSyntaxError("Malformed arguments to url tag")
name, value = match.groups()
if name:
kwargs[name] = parser.compile_filter(value)
else:
end = 0
for i, match in enumerate(url_arg_re.finditer(bit)):
if (i == 0 and match.start() != 0) or \
(i > 0 and (bit[end:match.start()] != ',')):
raise TemplateSyntaxError("Malformed arguments to url tag")
end = match.end()
name, value = match.group(1), match.group(2)
if name:
kwargs[name] = parser.compile_filter(value)
else:
args.append(parser.compile_filter(value))
if end != len(bit):
raise TemplateSyntaxError("Malformed arguments to url tag")
args.append(parser.compile_filter(value))
return URLNode(viewname, args, kwargs, asvar)
url = register.tag(url)

View File

@ -904,7 +904,7 @@ Returns an absolute URL (i.e., a URL without the domain name) matching a given
view function and optional parameters. This is a way to output links without
violating the DRY principle by having to hard-code URLs in your templates::
{% url path.to.some_view v1,v2 %}
{% url path.to.some_view v1 v2 %}
The first argument is a path to a view function in the format
``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.
The example above shows passing positional arguments. Alternatively you may
use keyword syntax::
{% url path.to.some_view arg1=v1,arg2=v2 %}
{% url path.to.some_view arg1=v1 arg2=v2 %}
Do not mix both positional and keyword syntax in a single call. All arguments
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
different call::
{% url path.to.view arg, arg2 as the_url %}
{% url path.to.view arg arg2 as the_url %}
<a href="{{ the_url }}">I'm linking to {{ the_url }}</a>
@ -976,6 +976,20 @@ This will follow the normal :ref:`namespaced URL resolution strategy
<topics-http-reversing-url-namespaces>`, including using any hints provided
by the context as to the current application.
.. versionchanged:: 1.2
For backwards compatibility, the ``{% url %}`` tag also supports the
use of commas to separate arguments. You shouldn't use this in any new
projects, but for the sake of the people who are still using it,
here's what it looks like::
{% url path.to.view arg,arg2 %}
{% url path.to.view arg, arg2 %}
This syntax doesn't support the use of literal commas, or or equals
signs. Did we mention you shouldn't use this syntax in any new
projects?
.. templatetag:: widthratio
widthratio

View File

@ -205,20 +205,20 @@ class Templates(unittest.TestCase):
def test_extends_include_missing_baseloader(self):
"""
Tests that the correct template is identified as not existing
when {% extends %} specifies a template that does exist, but
Tests that the correct template is identified as not existing
when {% extends %} specifies a template that does exist, but
that template has an {% include %} of something that does not
exist. See #12787.
"""
# TEMPLATE_DEBUG must be true, otherwise the exception raised
# TEMPLATE_DEBUG must be true, otherwise the exception raised
# during {% include %} processing will be suppressed.
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True
old_loaders = loader.template_source_loaders
try:
# Test the base loader class via the app loader. load_template
# from base is used by all shipped loaders excepting cached,
# Test the base loader class via the app loader. load_template
# from base is used by all shipped loaders excepting cached,
# which has its own test.
loader.template_source_loaders = (app_directories.Loader(),)
@ -237,7 +237,7 @@ class Templates(unittest.TestCase):
def test_extends_include_missing_cachedloader(self):
"""
Same as test_extends_include_missing_baseloader, only tests
Same as test_extends_include_missing_baseloader, only tests
behavior of the cached loader instead of BaseLoader.
"""
@ -1173,9 +1173,15 @@ class Templates(unittest.TestCase):
### URL TAG ########################################################
# Successes
'legacyurl02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'legacyurl02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'legacyurl10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
'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/'),
'legacyurl14': ('{% url regressiontests.templates.views.client_action client.id, arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
'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 @@ class Templates(unittest.TestCase):
'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/'),
'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
'url11': ('{% url regressiontests.templates.views.client_action id=client.id,action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
'url12': ('{% url regressiontests.templates.views.client_action id=client.id,action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
'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/'),
'url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
'url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
'url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
'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/'),
'url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'),
# Failures
'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),