mirror of
https://github.com/django/django.git
synced 2024-12-23 09:36:06 +00:00
Fixed #5034 -- honor request.urlconf in reverse and resolve.
This enables {% url %} to honor request.urlconf set from process_request middleware methods. Thanks SmileyChris for the initial patch work. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11740 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
c169f8cb17
commit
6c61ca3d74
@ -68,6 +68,9 @@ class BaseHandler(object):
|
|||||||
from django.core import exceptions, urlresolvers
|
from django.core import exceptions, urlresolvers
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Reset the urlconf for this thread.
|
||||||
|
urlresolvers.set_urlconf(None)
|
||||||
|
|
||||||
# Apply request middleware
|
# Apply request middleware
|
||||||
for middleware_method in self._request_middleware:
|
for middleware_method in self._request_middleware:
|
||||||
response = middleware_method(request)
|
response = middleware_method(request)
|
||||||
@ -77,61 +80,69 @@ class BaseHandler(object):
|
|||||||
# Get urlconf from request object, if available. Otherwise use default.
|
# Get urlconf from request object, if available. Otherwise use default.
|
||||||
urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
|
urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
|
||||||
|
|
||||||
|
# Set the urlconf for this thread to the one specified above.
|
||||||
|
urlresolvers.set_urlconf(urlconf)
|
||||||
|
|
||||||
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
||||||
try:
|
try:
|
||||||
callback, callback_args, callback_kwargs = resolver.resolve(
|
|
||||||
request.path_info)
|
|
||||||
|
|
||||||
# Apply view middleware
|
|
||||||
for middleware_method in self._view_middleware:
|
|
||||||
response = middleware_method(request, callback, callback_args, callback_kwargs)
|
|
||||||
if response:
|
|
||||||
return response
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = callback(request, *callback_args, **callback_kwargs)
|
callback, callback_args, callback_kwargs = resolver.resolve(
|
||||||
except Exception, e:
|
request.path_info)
|
||||||
# If the view raised an exception, run it through exception
|
|
||||||
# middleware, and if the exception middleware returns a
|
# Apply view middleware
|
||||||
# response, use that. Otherwise, reraise the exception.
|
for middleware_method in self._view_middleware:
|
||||||
for middleware_method in self._exception_middleware:
|
response = middleware_method(request, callback, callback_args, callback_kwargs)
|
||||||
response = middleware_method(request, e)
|
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
raise
|
|
||||||
|
|
||||||
# Complain if the view returned None (a common error).
|
|
||||||
if response is None:
|
|
||||||
try:
|
try:
|
||||||
view_name = callback.func_name # If it's a function
|
response = callback(request, *callback_args, **callback_kwargs)
|
||||||
except AttributeError:
|
except Exception, e:
|
||||||
view_name = callback.__class__.__name__ + '.__call__' # If it's a class
|
# If the view raised an exception, run it through exception
|
||||||
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
|
# middleware, and if the exception middleware returns a
|
||||||
|
# response, use that. Otherwise, reraise the exception.
|
||||||
|
for middleware_method in self._exception_middleware:
|
||||||
|
response = middleware_method(request, e)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
raise
|
||||||
|
|
||||||
return response
|
# Complain if the view returned None (a common error).
|
||||||
except http.Http404, e:
|
if response is None:
|
||||||
if settings.DEBUG:
|
|
||||||
from django.views import debug
|
|
||||||
return debug.technical_404_response(request, e)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
callback, param_dict = resolver.resolve404()
|
|
||||||
return callback(request, **param_dict)
|
|
||||||
except:
|
|
||||||
try:
|
try:
|
||||||
return self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
view_name = callback.func_name # If it's a function
|
||||||
finally:
|
except AttributeError:
|
||||||
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
|
view_name = callback.__class__.__name__ + '.__call__' # If it's a class
|
||||||
except exceptions.PermissionDenied:
|
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
|
||||||
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
|
|
||||||
except SystemExit:
|
return response
|
||||||
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
except http.Http404, e:
|
||||||
raise
|
if settings.DEBUG:
|
||||||
except: # Handle everything else, including SuspiciousOperation, etc.
|
from django.views import debug
|
||||||
# Get the exception info now, in case another exception is thrown later.
|
return debug.technical_404_response(request, e)
|
||||||
exc_info = sys.exc_info()
|
else:
|
||||||
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
|
try:
|
||||||
return self.handle_uncaught_exception(request, resolver, exc_info)
|
callback, param_dict = resolver.resolve404()
|
||||||
|
return callback(request, **param_dict)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
return self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||||
|
finally:
|
||||||
|
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||||
|
except exceptions.PermissionDenied:
|
||||||
|
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
|
||||||
|
except SystemExit:
|
||||||
|
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
||||||
|
raise
|
||||||
|
except: # Handle everything else, including SuspiciousOperation, etc.
|
||||||
|
# Get the exception info now, in case another exception is thrown later.
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||||
|
return self.handle_uncaught_exception(request, resolver, exc_info)
|
||||||
|
finally:
|
||||||
|
# Reset URLconf for this thread on the way out for complete
|
||||||
|
# isolation of request.urlconf
|
||||||
|
urlresolvers.set_urlconf(None)
|
||||||
|
|
||||||
def handle_uncaught_exception(self, request, resolver, exc_info):
|
def handle_uncaught_exception(self, request, resolver, exc_info):
|
||||||
"""
|
"""
|
||||||
|
@ -10,6 +10,7 @@ a string) and returns a tuple in this format:
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||||
from django.utils.datastructures import MultiValueDict
|
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
|
||||||
@ -32,6 +33,9 @@ _callable_cache = {} # Maps view and url pattern names to their view functions.
|
|||||||
# be empty.
|
# be empty.
|
||||||
_prefixes = {}
|
_prefixes = {}
|
||||||
|
|
||||||
|
# Overridden URLconfs for each thread are stored here.
|
||||||
|
_urlconfs = {}
|
||||||
|
|
||||||
class Resolver404(Http404):
|
class Resolver404(Http404):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -300,9 +304,13 @@ class RegexURLResolver(object):
|
|||||||
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
|
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
|
||||||
|
|
||||||
def resolve(path, urlconf=None):
|
def resolve(path, urlconf=None):
|
||||||
|
if urlconf is None:
|
||||||
|
urlconf = get_urlconf()
|
||||||
return get_resolver(urlconf).resolve(path)
|
return get_resolver(urlconf).resolve(path)
|
||||||
|
|
||||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
|
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
|
||||||
|
if urlconf is None:
|
||||||
|
urlconf = get_urlconf()
|
||||||
resolver = get_resolver(urlconf)
|
resolver = get_resolver(urlconf)
|
||||||
args = args or []
|
args = args or []
|
||||||
kwargs = kwargs or {}
|
kwargs = kwargs or {}
|
||||||
@ -371,3 +379,25 @@ def get_script_prefix():
|
|||||||
"""
|
"""
|
||||||
return _prefixes.get(currentThread(), u'/')
|
return _prefixes.get(currentThread(), u'/')
|
||||||
|
|
||||||
|
def set_urlconf(urlconf_name):
|
||||||
|
"""
|
||||||
|
Sets the URLconf for the current thread (overriding the default one in
|
||||||
|
settings). Set to None to revert back to the default.
|
||||||
|
"""
|
||||||
|
thread = currentThread()
|
||||||
|
if urlconf_name:
|
||||||
|
_urlconfs[thread] = urlconf_name
|
||||||
|
else:
|
||||||
|
# faster than wrapping in a try/except
|
||||||
|
if thread in _urlconfs:
|
||||||
|
del _urlconfs[thread]
|
||||||
|
|
||||||
|
def get_urlconf(default=None):
|
||||||
|
"""
|
||||||
|
Returns the root URLconf to use for the current thread if it has been
|
||||||
|
changed from the default one.
|
||||||
|
"""
|
||||||
|
thread = currentThread()
|
||||||
|
if thread in _urlconfs:
|
||||||
|
return _urlconfs[thread]
|
||||||
|
return default
|
||||||
|
@ -40,7 +40,8 @@ algorithm the system follows to determine which Python code to execute:
|
|||||||
|
|
||||||
1. Django determines the root URLconf module to use. Ordinarily,
|
1. Django determines the root URLconf module to use. Ordinarily,
|
||||||
this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
|
this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
|
||||||
``HttpRequest`` object has an attribute called ``urlconf``, its value
|
``HttpRequest`` object has an attribute called ``urlconf`` (set by
|
||||||
|
middleware :ref:`request processing <request-middleware>`), its value
|
||||||
will be used in place of the ``ROOT_URLCONF`` setting.
|
will be used in place of the ``ROOT_URLCONF`` setting.
|
||||||
|
|
||||||
2. Django loads that Python module and looks for the variable
|
2. Django loads that Python module and looks for the variable
|
||||||
|
7
tests/regressiontests/urlpatterns_reverse/middleware.py
Normal file
7
tests/regressiontests/urlpatterns_reverse/middleware.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.core.urlresolvers import set_urlconf
|
||||||
|
|
||||||
|
import urlconf_inner
|
||||||
|
|
||||||
|
class ChangeURLconfMiddleware(object):
|
||||||
|
def process_request(self, request):
|
||||||
|
request.urlconf = urlconf_inner.__name__
|
@ -16,11 +16,16 @@ ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.n
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
|
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
|
||||||
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
|
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
import urlconf_outer
|
||||||
|
import urlconf_inner
|
||||||
|
import middleware
|
||||||
|
|
||||||
test_data = (
|
test_data = (
|
||||||
('places', '/places/3/', [3], {}),
|
('places', '/places/3/', [3], {}),
|
||||||
('places', '/places/3/', ['3'], {}),
|
('places', '/places/3/', ['3'], {}),
|
||||||
@ -239,3 +244,35 @@ class NamespaceTests(TestCase):
|
|||||||
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
|
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
|
||||||
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
|
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
|
||||||
|
|
||||||
|
|
||||||
|
class RequestURLconfTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.root_urlconf = settings.ROOT_URLCONF
|
||||||
|
self.middleware_classes = settings.MIDDLEWARE_CLASSES
|
||||||
|
settings.ROOT_URLCONF = urlconf_outer.__name__
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
settings.ROOT_URLCONF = self.root_urlconf
|
||||||
|
settings.MIDDLEWARE_CLASSES = self.middleware_classes
|
||||||
|
|
||||||
|
def test_urlconf(self):
|
||||||
|
response = self.client.get('/test/me/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.content, 'outer:/test/me/,'
|
||||||
|
'inner:/inner_urlconf/second_test/')
|
||||||
|
response = self.client.get('/inner_urlconf/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get('/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_urlconf_overridden(self):
|
||||||
|
settings.MIDDLEWARE_CLASSES += (
|
||||||
|
'%s.ChangeURLconfMiddleware' % middleware.__name__,
|
||||||
|
)
|
||||||
|
response = self.client.get('/test/me/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
response = self.client.get('/inner_urlconf/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
response = self.client.get('/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.content, 'outer:,inner:/second_test/')
|
||||||
|
12
tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
Normal file
12
tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.conf.urls.defaults import *
|
||||||
|
from django.template import Template, Context
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
def inner_view(request):
|
||||||
|
content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
|
||||||
|
'{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
|
||||||
|
return HttpResponse(content)
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^second_test/$', inner_view, name='inner'),
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
import urlconf_inner
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^test/me/$', urlconf_inner.inner_view, name='outer'),
|
||||||
|
url(r'^inner_urlconf/', include(urlconf_inner.__name__))
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user