Loading django/core/urlresolvers.py +21 −1 Original line number Diff line number Diff line Loading @@ -230,6 +230,10 @@ class RegexURLResolver(LocaleRegexProvider): self._reverse_dict = {} self._namespace_dict = {} self._app_dict = {} # set of dotted paths to all functions and classes that are used in # urlpatterns self._callback_strs = set() self._populated = False def __repr__(self): return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) Loading @@ -240,6 +244,15 @@ class RegexURLResolver(LocaleRegexProvider): apps = {} language_code = get_language() for pattern in reversed(self.url_patterns): if hasattr(pattern, '_callback_str'): self._callback_strs.add(pattern._callback_str) elif hasattr(pattern, '_callback'): callback = pattern._callback if not hasattr(callback, '__name__'): lookup_str = callback.__module__ + "." + callback.__class__.__name__ else: lookup_str = callback.__module__ + "." + callback.__name__ self._callback_strs.add(lookup_str) p_pattern = pattern.regex.pattern if p_pattern.startswith('^'): p_pattern = p_pattern[1:] Loading @@ -260,6 +273,7 @@ class RegexURLResolver(LocaleRegexProvider): namespaces[namespace] = (p_pattern + prefix, sub_pattern) for app_name, namespace_list in pattern.app_dict.items(): apps.setdefault(app_name, []).extend(namespace_list) self._callback_strs.update(pattern._callback_strs) else: bits = normalize(p_pattern) lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args)) Loading @@ -268,6 +282,7 @@ class RegexURLResolver(LocaleRegexProvider): self._reverse_dict[language_code] = lookups self._namespace_dict[language_code] = namespaces self._app_dict[language_code] = apps self._populated = True @property def reverse_dict(self): Loading Loading @@ -356,7 +371,12 @@ class RegexURLResolver(LocaleRegexProvider): def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): if args and kwargs: raise ValueError("Don't mix *args and **kwargs in call to reverse()!") if not self._populated: self._populate() try: if lookup_view in self._callback_strs: lookup_view = get_callable(lookup_view, True) except (ImportError, AttributeError), e: raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) Loading tests/regressiontests/urlpatterns_reverse/nonimported_module.py 0 → 100644 +3 −0 Original line number Diff line number Diff line def view(request): """Stub view""" pass tests/regressiontests/urlpatterns_reverse/tests.py +22 −1 Original line number Diff line number Diff line # -*- coding: utf-8 -*- """ Unit tests for reverse URL lookups. """ from __future__ import absolute_import import sys from django.conf import settings from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.urlresolvers import (reverse, resolve, NoReverseMatch, Loading Loading @@ -267,6 +270,25 @@ class ReverseShortcutTests(TestCase): self.assertEqual(res['Location'], '/foo/') res = redirect('http://example.com/') self.assertEqual(res['Location'], 'http://example.com/') # Assert that we can redirect using UTF-8 strings res = redirect('/æøå/abc/') self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5/abc/') # Assert that no imports are attempted when dealing with a relative path # (previously, the below would resolve in a UnicodeEncodeError from __import__ ) res = redirect('/æøå.abc/') self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5.abc/') res = redirect('os.path') self.assertEqual(res['Location'], 'os.path') def test_no_illegal_imports(self): # modules that are not listed in urlpatterns should not be importable redirect("urlpatterns_reverse.nonimported_module.view") self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) def test_reverse_by_path_nested(self): # Views that are added to urlpatterns using include() should be # reversable by doted path. self.assertEqual(reverse('regressiontests.urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') def test_redirect_view_object(self): from .views import absolute_kwargs_view Loading Loading @@ -510,4 +532,3 @@ class ErroneousViewTests(TestCase): self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/') self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/') self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/') tests/regressiontests/urlpatterns_reverse/urls.py +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from .views import empty_view, absolute_kwargs_view other_patterns = patterns('', url(r'non_path_include/$', empty_view, name='non_path_include'), url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'), ) urlpatterns = patterns('', Loading tests/regressiontests/urlpatterns_reverse/views.py +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2): def defaults_view(request, arg1, arg2): pass def nested_view(request): pass def erroneous_view(request): import non_existent Loading Loading
django/core/urlresolvers.py +21 −1 Original line number Diff line number Diff line Loading @@ -230,6 +230,10 @@ class RegexURLResolver(LocaleRegexProvider): self._reverse_dict = {} self._namespace_dict = {} self._app_dict = {} # set of dotted paths to all functions and classes that are used in # urlpatterns self._callback_strs = set() self._populated = False def __repr__(self): return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) Loading @@ -240,6 +244,15 @@ class RegexURLResolver(LocaleRegexProvider): apps = {} language_code = get_language() for pattern in reversed(self.url_patterns): if hasattr(pattern, '_callback_str'): self._callback_strs.add(pattern._callback_str) elif hasattr(pattern, '_callback'): callback = pattern._callback if not hasattr(callback, '__name__'): lookup_str = callback.__module__ + "." + callback.__class__.__name__ else: lookup_str = callback.__module__ + "." + callback.__name__ self._callback_strs.add(lookup_str) p_pattern = pattern.regex.pattern if p_pattern.startswith('^'): p_pattern = p_pattern[1:] Loading @@ -260,6 +273,7 @@ class RegexURLResolver(LocaleRegexProvider): namespaces[namespace] = (p_pattern + prefix, sub_pattern) for app_name, namespace_list in pattern.app_dict.items(): apps.setdefault(app_name, []).extend(namespace_list) self._callback_strs.update(pattern._callback_strs) else: bits = normalize(p_pattern) lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args)) Loading @@ -268,6 +282,7 @@ class RegexURLResolver(LocaleRegexProvider): self._reverse_dict[language_code] = lookups self._namespace_dict[language_code] = namespaces self._app_dict[language_code] = apps self._populated = True @property def reverse_dict(self): Loading Loading @@ -356,7 +371,12 @@ class RegexURLResolver(LocaleRegexProvider): def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): if args and kwargs: raise ValueError("Don't mix *args and **kwargs in call to reverse()!") if not self._populated: self._populate() try: if lookup_view in self._callback_strs: lookup_view = get_callable(lookup_view, True) except (ImportError, AttributeError), e: raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) Loading
tests/regressiontests/urlpatterns_reverse/nonimported_module.py 0 → 100644 +3 −0 Original line number Diff line number Diff line def view(request): """Stub view""" pass
tests/regressiontests/urlpatterns_reverse/tests.py +22 −1 Original line number Diff line number Diff line # -*- coding: utf-8 -*- """ Unit tests for reverse URL lookups. """ from __future__ import absolute_import import sys from django.conf import settings from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.urlresolvers import (reverse, resolve, NoReverseMatch, Loading Loading @@ -267,6 +270,25 @@ class ReverseShortcutTests(TestCase): self.assertEqual(res['Location'], '/foo/') res = redirect('http://example.com/') self.assertEqual(res['Location'], 'http://example.com/') # Assert that we can redirect using UTF-8 strings res = redirect('/æøå/abc/') self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5/abc/') # Assert that no imports are attempted when dealing with a relative path # (previously, the below would resolve in a UnicodeEncodeError from __import__ ) res = redirect('/æøå.abc/') self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5.abc/') res = redirect('os.path') self.assertEqual(res['Location'], 'os.path') def test_no_illegal_imports(self): # modules that are not listed in urlpatterns should not be importable redirect("urlpatterns_reverse.nonimported_module.view") self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) def test_reverse_by_path_nested(self): # Views that are added to urlpatterns using include() should be # reversable by doted path. self.assertEqual(reverse('regressiontests.urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') def test_redirect_view_object(self): from .views import absolute_kwargs_view Loading Loading @@ -510,4 +532,3 @@ class ErroneousViewTests(TestCase): self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/') self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/') self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
tests/regressiontests/urlpatterns_reverse/urls.py +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from .views import empty_view, absolute_kwargs_view other_patterns = patterns('', url(r'non_path_include/$', empty_view, name='non_path_include'), url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'), ) urlpatterns = patterns('', Loading
tests/regressiontests/urlpatterns_reverse/views.py +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2): def defaults_view(request, arg1, arg2): pass def nested_view(request): pass def erroneous_view(request): import non_existent Loading