Fixed #10802 -- Handle ImportErrors and AttributeErrors gracefully when raised by the URL resolver system during startup. Many thanks, IonelMaries and Bas Peschier.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16420 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
30e842632e
commit
b4cdf4d111
|
@ -16,6 +16,7 @@ from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
|
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
|
||||||
from django.utils.functional import memoize, lazy
|
from django.utils.functional import memoize, lazy
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
from django.utils.module_loading import module_has_submodule
|
||||||
from django.utils.regex_helper import normalize
|
from django.utils.regex_helper import normalize
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
|
||||||
|
@ -84,19 +85,28 @@ def get_callable(lookup_view, can_fail=False):
|
||||||
during the import fail and the string is returned.
|
during the import fail and the string is returned.
|
||||||
"""
|
"""
|
||||||
if not callable(lookup_view):
|
if not callable(lookup_view):
|
||||||
try:
|
|
||||||
# Bail early for non-ASCII strings (they can't be functions).
|
|
||||||
lookup_view = lookup_view.encode('ascii')
|
|
||||||
mod_name, func_name = get_mod_func(lookup_view)
|
mod_name, func_name = get_mod_func(lookup_view)
|
||||||
|
try:
|
||||||
if func_name != '':
|
if func_name != '':
|
||||||
lookup_view = getattr(import_module(mod_name), func_name)
|
lookup_view = getattr(import_module(mod_name), func_name)
|
||||||
if not callable(lookup_view):
|
if not callable(lookup_view):
|
||||||
raise AttributeError("'%s.%s' is not a callable." % (mod_name, func_name))
|
raise ViewDoesNotExist(
|
||||||
except (ImportError, AttributeError):
|
"Could not import %s.%s. View is not callable."
|
||||||
|
% (mod_name, func_name))
|
||||||
|
except AttributeError:
|
||||||
|
if not can_fail:
|
||||||
|
raise ViewDoesNotExist(
|
||||||
|
"Could not import %s. View does not exist in module %s."
|
||||||
|
% (lookup_view, mod_name))
|
||||||
|
except ImportError:
|
||||||
|
ownermod, submod = get_mod_func(mod_name)
|
||||||
|
if (not can_fail and submod != '' and
|
||||||
|
not module_has_submodule(import_module(ownermod), submod)):
|
||||||
|
raise ViewDoesNotExist(
|
||||||
|
"Could not import %s. Owning module %s does not exist."
|
||||||
|
% (lookup_view, mod_name))
|
||||||
if not can_fail:
|
if not can_fail:
|
||||||
raise
|
raise
|
||||||
except UnicodeEncodeError:
|
|
||||||
pass
|
|
||||||
return lookup_view
|
return lookup_view
|
||||||
get_callable = memoize(get_callable, _callable_cache, 1)
|
get_callable = memoize(get_callable, _callable_cache, 1)
|
||||||
|
|
||||||
|
@ -192,14 +202,8 @@ class RegexURLPattern(LocaleRegexProvider):
|
||||||
def callback(self):
|
def callback(self):
|
||||||
if self._callback is not None:
|
if self._callback is not None:
|
||||||
return self._callback
|
return self._callback
|
||||||
try:
|
|
||||||
self._callback = get_callable(self._callback_str)
|
self._callback = get_callable(self._callback_str)
|
||||||
except ImportError, e:
|
|
||||||
mod_name, _ = get_mod_func(self._callback_str)
|
|
||||||
raise ViewDoesNotExist("Could not import %s. Error was: %s" % (mod_name, str(e)))
|
|
||||||
except AttributeError, e:
|
|
||||||
mod_name, func_name = get_mod_func(self._callback_str)
|
|
||||||
raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)))
|
|
||||||
return self._callback
|
return self._callback
|
||||||
|
|
||||||
class RegexURLResolver(LocaleRegexProvider):
|
class RegexURLResolver(LocaleRegexProvider):
|
||||||
|
@ -325,10 +329,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
# Lazy import, since urls.defaults imports this file
|
# Lazy import, since urls.defaults imports this file
|
||||||
from django.conf.urls import defaults
|
from django.conf.urls import defaults
|
||||||
callback = getattr(defaults, 'handler%s' % view_type)
|
callback = getattr(defaults, 'handler%s' % view_type)
|
||||||
try:
|
|
||||||
return get_callable(callback), {}
|
return get_callable(callback), {}
|
||||||
except (ImportError, AttributeError), e:
|
|
||||||
raise ViewDoesNotExist("Tried %s. Error was: %s" % (callback, str(e)))
|
|
||||||
|
|
||||||
def resolve404(self):
|
def resolve404(self):
|
||||||
return self._resolve_special('404')
|
return self._resolve_special('404')
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
# View has erroneous import
|
||||||
|
url(r'erroneous_inner/$', 'regressiontests.urlpatterns_reverse.views.erroneous_view'),
|
||||||
|
# Module has erroneous import
|
||||||
|
url(r'erroneous_outer/$', 'regressiontests.urlpatterns_reverse.erroneous_views_module.erroneous_view'),
|
||||||
|
# View does not exist
|
||||||
|
url(r'missing_inner/$', 'regressiontests.urlpatterns_reverse.views.missing_view'),
|
||||||
|
# View is not callable
|
||||||
|
url(r'uncallable/$', 'regressiontests.urlpatterns_reverse.views.uncallable'),
|
||||||
|
# Module does not exist
|
||||||
|
url(r'missing_outer/$', 'regressiontests.urlpatterns_reverse.missing_module.missing_view'),
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
import non_existent
|
||||||
|
|
||||||
|
def erroneous_view(request):
|
||||||
|
pass
|
|
@ -2,7 +2,7 @@
|
||||||
Unit tests for reverse URL lookups.
|
Unit tests for reverse URL lookups.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||||
from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
|
from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
|
||||||
Resolver404, ResolverMatch,\
|
Resolver404, ResolverMatch,\
|
||||||
RegexURLResolver, RegexURLPattern
|
RegexURLResolver, RegexURLPattern
|
||||||
|
@ -470,3 +470,14 @@ class ResolverMatchTests(TestCase):
|
||||||
self.assertEqual(match[0], func)
|
self.assertEqual(match[0], func)
|
||||||
self.assertEqual(match[1], args)
|
self.assertEqual(match[1], args)
|
||||||
self.assertEqual(match[2], kwargs)
|
self.assertEqual(match[2], kwargs)
|
||||||
|
|
||||||
|
class ErroneousViewTests(TestCase):
|
||||||
|
urls = 'regressiontests.urlpatterns_reverse.erroneous_urls'
|
||||||
|
|
||||||
|
def test_erroneous_resolve(self):
|
||||||
|
self.assertRaises(ImportError, self.client.get, '/erroneous_inner/')
|
||||||
|
self.assertRaises(ImportError, self.client.get, '/erroneous_outer/')
|
||||||
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/')
|
||||||
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
|
||||||
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,11 @@ def absolute_kwargs_view(request, arg1=1, arg2=2):
|
||||||
def defaults_view(request, arg1, arg2):
|
def defaults_view(request, arg1, arg2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def erroneous_view(request):
|
||||||
|
import non_existent
|
||||||
|
|
||||||
|
uncallable = "Can I be a view? Pleeeease?"
|
||||||
|
|
||||||
class ViewClass(object):
|
class ViewClass(object):
|
||||||
def __call__(self, request, *args, **kwargs):
|
def __call__(self, request, *args, **kwargs):
|
||||||
return HttpResponse('')
|
return HttpResponse('')
|
||||||
|
|
Loading…
Reference in New Issue