Ticket #16774: #16774-Backtracking_URL_resolver.diff

File #16774-Backtracking_URL_resolver.diff, 9.8 KB (added by German M. Bravo, 10 years ago)
  • django/core/handlers/base.py

    diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
    index 5911865..4c9c05b 100644
    a b class BaseHandler(object):  
    98144                    urlresolvers.set_urlconf(urlconf)
    99145                    resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
    100146
    101                 resolver_match = resolver.resolve(request.path_info)
     147            # Use resolved to try to resolve matching URL patterns one by one
     148            resolved = {}
     149
     150            while response is None:
     151                resolver_match = resolver.resolve(request.path_info, resolved=resolved)
    102152                callback, callback_args, callback_kwargs = resolver_match
    103153                request.resolver_match = resolver_match
    104154
    class BaseHandler(object):  
    108158                    if response:
    109159                        break
    110160
    111             if response is None:
    112                 wrapped_callback = self.make_view_atomic(callback)
    113                 try:
    114                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
    115                 except Exception as e:
    116                     # If the view raised an exception, run it through exception
    117                     # middleware, and if the exception middleware returns a
    118                     # response, use that. Otherwise, reraise the exception.
    119                     for middleware_method in self._exception_middleware:
    120                         response = middleware_method(request, e)
    121                         if response:
    122                             break
    123                     if response is None:
    124                         raise
     161                if response is None:
     162                    wrapped_callback = self.make_view_atomic(callback)
     163                    try:
     164                        response = wrapped_callback(request, *callback_args, **callback_kwargs)
     165                    except urlresolvers.DoesNotResolve:
     166                        # Continue resolve URLs if the view raises
     167                        # urlresolvers.DoesNotResolve exception to indicate
     168                        # the url pattern does not match.
     169                        continue
     170                    except Exception as e:
     171                        # If the view raised an exception, run it through exception
     172                        # middleware, and if the exception middleware returns a
     173                        # response, use that. Otherwise, reraise the exception.
     174                        for middleware_method in self._exception_middleware:
     175                            response = middleware_method(request, e)
     176                            if response:
     177                                break
     178                        if response is None:
     179                            raise
    125180
    126181            # Complain if the view returned None (a common error).
    127182            if response is None:
  • django/core/urlresolvers.py

    diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
    index af3df83..055136e 100644
    a b def __repr__(self):  
    7373class Resolver404(Http404):
    7474    pass
    7575
     76class DoesNotResolve(Http404):
     77    pass
     78
    7679class NoReverseMatch(Exception):
    7780    # Don't make this raise an error when used in a template.
    7881    silent_variable_failure = True
    class RegexURLPattern(LocaleRegexProvider):  
    205210            return
    206211        self._callback_str = prefix + '.' + self._callback_str
    207212
    208     def resolve(self, path):
     213    def resolve(self, path, resolved=None, caller=None):
     214        if resolved is not None:
     215            if caller not in resolved:
     216                resolved[caller] = set()
     217            resolved[caller].add(self)
    209218        match = self.regex.search(path)
    210219        if match:
    211220            # If there are any named groups, use those as kwargs, ignoring
    class RegexURLResolver(LocaleRegexProvider):  
    310318            self._populate()
    311319        return self._app_dict[language_code]
    312320
    313     def resolve(self, path):
     321    def resolve(self, path, resolved=None, caller=None):
    314322        tried = []
     323        if resolved is None:
     324            resolved = {}
     325        if caller not in resolved:
     326            resolved[caller] = set()
    315327        match = self.regex.search(path)
    316328        if match:
     329            if self not in resolved:
     330                resolved[self] = set()
    317331            new_path = path[match.end():]
    318332            for pattern in self.url_patterns:
     333                if pattern in resolved[self]:
     334                    continue
    319335                try:
    320                     sub_match = pattern.resolve(new_path)
     336                    sub_match = pattern.resolve(new_path, resolved=resolved, caller=self)
    321337                except Resolver404 as e:
    322338                    sub_tried = e.args[0].get('tried')
    323339                    if sub_tried is not None:
    class RegexURLResolver(LocaleRegexProvider):  
    330344                        sub_match_dict.update(sub_match.kwargs)
    331345                        return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
    332346                    tried.append([pattern])
     347                resolved[self].add(pattern)
     348            del resolved[self]
     349            resolved[caller].add(self)
    333350            raise Resolver404({'tried': tried, 'path': new_path})
     351        resolved[caller].add(self)
    334352        raise Resolver404({'path' : path})
    335353
    336354    @property
  • tests/view_tests/tests/test_specials.py

    a b class URLHandling(TestCase):  
    3737        response = self.client.get('/permanent_nonascii_redirect/')
    3838        self.assertRedirects(response, self.redirect_target, status_code=301)
    3939
     40    def test_overlapping_urls_reverse(self):
     41        from django.core import urlresolvers
     42        url = urlresolvers.reverse('overlapping_view1', kwargs={'title': 'sometitle'})
     43        self.assertEqual(url, '/overlapping_view/sometitle/')
     44        url = urlresolvers.reverse('overlapping_view2', kwargs={'author': 'someauthor'})
     45        self.assertEqual(url, '/overlapping_view/someauthor/')
     46
     47    def test_overlapping_urls_resolve(self):
     48        response = self.client.get('/overlapping_view/sometitle/')
     49        self.assertContains(response, 'overlapping_view2')
     50
     51    def test_overlapping_urls_not_resolve(self):
     52        response = self.client.get('/no_overlapping_view/sometitle/')
     53        self.assertEqual(response.status_code, 404)
     54
     55    def test_nested_overlapping_urls_reverse(self):
     56        from django.core import urlresolvers
     57        url = urlresolvers.reverse('nested_overlapping_view1', kwargs={'title': 'sometitle'})
     58        self.assertEqual(url, '/nested/overlapping_view/sometitle/')
     59        url = urlresolvers.reverse('nested_overlapping_view2', kwargs={'author': 'someauthor'})
     60        self.assertEqual(url, '/nested/overlapping_view/someauthor/')
     61
     62    def test_nested_overlapping_urls_resolve(self):
     63        response = self.client.get('/nested/overlapping_view/sometitle/')
     64        self.assertContains(response, 'overlapping_view2')
     65
     66    def test_nested_overlapping_urls_not_resolve(self):
     67        response = self.client.get('/nested/no_overlapping_view/sometitle/')
     68        self.assertEqual(response.status_code, 404)
  • tests/view_tests/generic_urls.py

    a b  
    11# -*- coding:utf-8 -*-
    22from __future__ import absolute_import, unicode_literals
    33
    4 from django.conf.urls import patterns, url
     4from django.conf.urls import patterns, url, include
    55from django.views.generic import RedirectView
    66
    77from . import views
    urlpatterns += patterns('view_tests.views',  
    5454    (r'^shortcuts/render/status/$', 'render_view_with_status'),
    5555    (r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
    5656    (r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
     57    (r'^nested/', include(patterns('view_tests.views',
     58        url(r'^overlapping_view/(?P<title>[a-z]+)/$', 'overlapping_view1', name='nested_overlapping_view1'),
     59        url(r'^overlapping_view/(?P<author>[a-z]+)/$', 'overlapping_view2', name='nested_overlapping_view2'),
     60        url(r'^overlapping_view/(?P<keywords>[a-z]+)/$', 'overlapping_view3', name='nested_overlapping_view3'),
     61        url(r'^no_overlapping_view/(?P<keywords>[a-z]+)/$', 'no_overlapping_view', name='nested_no_overlapping_view'),
     62    ))),
     63    url(r'^overlapping_view/(?P<title>[a-z]+)/$', 'overlapping_view1', name='overlapping_view1'),
     64    url(r'^overlapping_view/(?P<author>[a-z]+)/$', 'overlapping_view2', name='overlapping_view2'),
     65    url(r'^overlapping_view/(?P<keywords>[a-z]+)/$', 'overlapping_view3', name='overlapping_view3'),
     66    url(r'^no_overlapping_view/(?P<keywords>[a-z]+)/$', 'no_overlapping_view', name='no_overlapping_view'),
    5767)
  • tests/view_tests/views.py

    a b from __future__ import absolute_import  
    33import sys
    44
    55from django.core.exceptions import PermissionDenied, SuspiciousOperation
    6 from django.core.urlresolvers import get_resolver
     6from django.core.urlresolvers import get_resolver, DoesNotResolve
    77from django.http import HttpResponse, HttpResponseRedirect
    88from django.shortcuts import render_to_response, render
    99from django.template import Context, RequestContext, TemplateDoesNotExist
    class Klass(object):  
    273273def sensitive_method_view(request):
    274274    return Klass().method(request)
    275275
     276def overlapping_view1(request, title=None):
     277    raise DoesNotResolve
     278
     279def overlapping_view2(request, author=None):
     280    return HttpResponse("overlapping_view2")
     281
     282def overlapping_view3(request, keywords=None):
     283    return HttpResponse("overlapping_view3")
     284
     285def no_overlapping_view(request, keywords=None):
     286    raise DoesNotResolve
    276287
    277288@sensitive_variables('sauce')
    278289@sensitive_post_parameters('bacon-key', 'sausage-key')
Back to Top