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:
Jannis Leidel 2011-06-16 16:41:14 +00:00
parent 30e842632e
commit b4cdf4d111
5 changed files with 55 additions and 20 deletions

View File

@ -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):
mod_name, func_name = get_mod_func(lookup_view)
try: 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)
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')

View File

@ -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'),
)

View File

@ -0,0 +1,4 @@
import non_existent
def erroneous_view(request):
pass

View File

@ -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/')

View File

@ -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('')