Ticket #16774: #16774-Backtracking_URL_resolver.2.diff

File #16774-Backtracking_URL_resolver.2.diff, 9.9 KB (added by German M. Bravo, 5 years ago)

Updated for Django v1.7, also it keeps track of already tried urls

  • 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): 
    9696                    urlresolvers.set_urlconf(urlconf)
    9797                    resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
    9898
    99                 resolver_match = resolver.resolve(request.path_info)
     99            # Use resolved to try to resolve matching URL patterns one by one
     100            tried = []
     101            resolved = {}
     102
     103            while response is None:
     104                resolver_match = resolver.resolve(request.path_info, tried=tried, resolved=resolved)
    100105                callback, callback_args, callback_kwargs = resolver_match
    101106                request.resolver_match = resolver_match
    102107
    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
     79
    7680class NoReverseMatch(Exception):
    7781    # Don't make this raise an error when used in a template.
    7882    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): 
    329339            self._populate()
    330340        return self._app_dict[language_code]
    331341
    332     def resolve(self, path):
    333         tried = []
     342    def resolve(self, path, tried=None, resolved=None, caller=None):
     343        if tried is None:
     344            tried = []
     345        if resolved is None:
     346            resolved = {}
     347        if caller not in resolved:
     348            resolved[caller] = set()
    334349        match = self.regex.search(path)
    335350        if match:
     351            if self not in resolved:
     352                resolved[self] = set()
    336353            new_path = path[match.end():]
    337354            for pattern in self.url_patterns:
     355                if pattern in resolved[self]:
     356                    continue
    338357                try:
    339                     sub_match = pattern.resolve(new_path)
     358                    sub_match = pattern.resolve(new_path, resolved=resolved, caller=self)
    340359                except Resolver404 as e:
    341360                    sub_tried = e.args[0].get('tried')
    342361                    if sub_tried is not None:
    class RegexURLResolver(LocaleRegexProvider): 
    355372                        sub_match_dict.update(sub_match.kwargs)
    356373                        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)
    357374                    tried.append([pattern])
     375                resolved[self].add(pattern)
     376            del resolved[self]
     377            resolved[caller].add(self)
    358378            raise Resolver404({'tried': tried, 'path': new_path})
     379        resolved[caller].add(self)
    359380        raise Resolver404({'path': path})
    360381
    361382    @property
  • tests/view_tests/tests/test_specials.py

    a b class URLHandling(TestCase): 
    3636        """
    3737        response = self.client.get('/permanent_nonascii_redirect/')
    3838        self.assertRedirects(response, self.redirect_target, status_code=301)
     39
     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 import os 
    66import sys
    77
    88from django.core.exceptions import PermissionDenied, SuspiciousOperation
    9 from django.core.urlresolvers import get_resolver
     9from django.core.urlresolvers import get_resolver, DoesNotResolve
    1010from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
    1111from django.shortcuts import render_to_response, render
    1212from 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