Ticket #9038: 9038-r9084.diff

File 9038-r9084.diff, 6.0 KB (added by russellm, 7 years ago)

Fix for URL reversing with tests

  • django/core/urlresolvers.py

     
    1111
    1212from django.http import Http404
    1313from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
     14from django.utils.datastructures import MultiValueDict
    1415from django.utils.encoding import iri_to_uri, force_unicode, smart_str
    1516from django.utils.functional import memoize
    1617from django.utils.regex_helper import normalize
     
    144145        self.urlconf_name = urlconf_name
    145146        self.callback = None
    146147        self.default_kwargs = default_kwargs or {}
    147         self._reverse_dict = {}
     148        self._reverse_dict = MultiValueDict()
    148149
    149150    def __repr__(self):
    150151        return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
     
    162163                        for piece, p_args in parent:
    163164                            new_matches.extend([(piece + suffix, p_args + args)
    164165                                    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))
    166167                else:
    167168                    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))
    170171        return self._reverse_dict
    171172    reverse_dict = property(_get_reverse_dict)
    172173
     
    223224            lookup_view = get_callable(lookup_view, True)
    224225        except (ImportError, AttributeError), e:
    225226            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
    240242        raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
    241243                "arguments '%s' not found." % (lookup_view, args, kwargs))
    242244
  • tests/regressiontests/urlpatterns_reverse/views.py

     
    11def empty_view(request, *args, **kwargs):
    22    pass
     3
     4def kwargs_view(request, arg1=1, arg2=2):
     5    pass
     6
     7def absolute_kwargs_view(request, arg1=1, arg2=2):
     8    pass
  • tests/regressiontests/urlpatterns_reverse/tests.py

     
    6565    ('extra-places', '/e-places/10/', ['10'], {}),
    6666    ('extra-people', '/e-people/fred/', ['fred'], {}),
    6767    ('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
    6879)
    6980
    7081class URLPatternReverse(TestCase):
  • tests/regressiontests/urlpatterns_reverse/urls.py

     
    11from django.conf.urls.defaults import *
    2 from views import empty_view
     2from views import empty_view, absolute_kwargs_view
    33
    44urlpatterns = patterns('',
    55    url(r'^places/(\d+)/$', empty_view, name='places'),
     
    4545
    4646    # This is non-reversible, but we shouldn't blow up when parsing it.
    4747    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
    4855)
Back to Top