Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #9038 -- Correctly handle URL patterns with the same name (or v…

…iew name),

declared independently and that differ only by argument signatures.

Patch from Russell Keith-Magee.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9087 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 15b0158e393544bd215688f257ab03604cd07950 1 parent 6c7cf34
Malcolm Tredinnick authored
38  django/core/urlresolvers.py
@@ -11,6 +11,7 @@
11 11
 
12 12
 from django.http import Http404
13 13
 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  14
+from django.utils.datastructures import MultiValueDict
14 15
 from django.utils.encoding import iri_to_uri, force_unicode, smart_str
15 16
 from django.utils.functional import memoize
16 17
 from django.utils.regex_helper import normalize
@@ -144,7 +145,7 @@ def __init__(self, regex, urlconf_name, default_kwargs=None):
144 145
         self.urlconf_name = urlconf_name
145 146
         self.callback = None
146 147
         self.default_kwargs = default_kwargs or {}
147  
-        self._reverse_dict = {}
  148
+        self._reverse_dict = MultiValueDict()
148 149
 
149 150
     def __repr__(self):
150 151
         return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
@@ -162,11 +163,11 @@ def _get_reverse_dict(self):
162 163
                         for piece, p_args in parent:
163 164
                             new_matches.extend([(piece + suffix, p_args + args)
164 165
                                     for (suffix, args) in matches])
165  
-                        self._reverse_dict[name] = new_matches, p_pattern + pat
  166
+                        self._reverse_dict.appendlist(name, (new_matches, p_pattern + pat))
166 167
                 else:
167 168
                     bits = normalize(p_pattern)
168  
-                    self._reverse_dict[pattern.callback] = bits, p_pattern
169  
-                    self._reverse_dict[pattern.name] = bits, p_pattern
  169
+                    self._reverse_dict.appendlist(pattern.callback, (bits, p_pattern))
  170
+                    self._reverse_dict.appendlist(pattern.name, (bits, p_pattern))
170 171
         return self._reverse_dict
171 172
     reverse_dict = property(_get_reverse_dict)
172 173
 
@@ -223,20 +224,21 @@ def reverse(self, lookup_view, *args, **kwargs):
223 224
             lookup_view = get_callable(lookup_view, True)
224 225
         except (ImportError, AttributeError), e:
225 226
             raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
226  
-        possibilities, pattern = self.reverse_dict.get(lookup_view, [(), ()])
227  
-        for result, params in possibilities:
228  
-            if args:
229  
-                if len(args) != len(params):
230  
-                    continue
231  
-                unicode_args = [force_unicode(val) for val in args]
232  
-                candidate =  result % dict(zip(params, unicode_args))
233  
-            else:
234  
-                if set(kwargs.keys()) != set(params):
235  
-                    continue
236  
-                unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
237  
-                candidate = result % unicode_kwargs
238  
-            if re.search(u'^%s' % pattern, candidate, re.UNICODE):
239  
-                return candidate
  227
+        possibilities = self.reverse_dict.getlist(lookup_view)
  228
+        for possibility, pattern in possibilities:
  229
+            for result, params in possibility:
  230
+                if args:
  231
+                    if len(args) != len(params):
  232
+                        continue
  233
+                    unicode_args = [force_unicode(val) for val in args]
  234
+                    candidate =  result % dict(zip(params, unicode_args))
  235
+                else:
  236
+                    if set(kwargs.keys()) != set(params):
  237
+                        continue
  238
+                    unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
  239
+                    candidate = result % unicode_kwargs
  240
+                if re.search(u'^%s' % pattern, candidate, re.UNICODE):
  241
+                    return candidate
240 242
         raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
241 243
                 "arguments '%s' not found." % (lookup_view, args, kwargs))
242 244
 
11  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -65,6 +65,17 @@
65 65
     ('extra-places', '/e-places/10/', ['10'], {}),
66 66
     ('extra-people', '/e-people/fred/', ['fred'], {}),
67 67
     ('extra-people', '/e-people/fred/', [], {'name': 'fred'}),
  68
+
  69
+    # Regression for #9038
  70
+    # These views are resolved by method name. Each method is deployed twice -
  71
+    # once with an explicit argument, and once using the default value on
  72
+    # the method. This is potentially ambiguous, as you have to pick the
  73
+    # correct view for the arguments provided.
  74
+    ('kwargs_view', '/arg_view/', [], {}),
  75
+    ('kwargs_view', '/arg_view/10/', [], {'arg1':10}),
  76
+    ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}),
  77
+    ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}),
  78
+
68 79
 )
69 80
 
70 81
 class URLPatternReverse(TestCase):
9  tests/regressiontests/urlpatterns_reverse/urls.py
... ...
@@ -1,5 +1,5 @@
1 1
 from django.conf.urls.defaults import *
2  
-from views import empty_view
  2
+from views import empty_view, absolute_kwargs_view
3 3
 
4 4
 urlpatterns = patterns('',
5 5
     url(r'^places/(\d+)/$', empty_view, name='places'),
@@ -45,4 +45,11 @@
45 45
 
46 46
     # This is non-reversible, but we shouldn't blow up when parsing it.
47 47
     url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"),
  48
+
  49
+    # Regression views for #9038. See tests for more details
  50
+    url(r'arg_view/$', 'kwargs_view'),
  51
+    url(r'arg_view/(?P<arg1>\d+)/$', 'kwargs_view'),
  52
+    url(r'absolute_arg_view/(?P<arg1>\d+)/$', absolute_kwargs_view),
  53
+    url(r'absolute_arg_view/$', absolute_kwargs_view),
  54
+
48 55
 )
6  tests/regressiontests/urlpatterns_reverse/views.py
... ...
@@ -1,2 +1,8 @@
1 1
 def empty_view(request, *args, **kwargs):
2 2
     pass
  3
+
  4
+def kwargs_view(request, arg1=1, arg2=2):
  5
+    pass
  6
+
  7
+def absolute_kwargs_view(request, arg1=1, arg2=2):
  8
+    pass

0 notes on commit 15b0158

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