Merge pull request #885 from loic/ticket19541
Fixed #19541 -- Fixed BaseHandler to enable reversing URLs in response middlewares...
This commit is contained in:
commit
24234555a0
|
@ -74,110 +74,109 @@ class BaseHandler(object):
|
||||||
|
|
||||||
def get_response(self, request):
|
def get_response(self, request):
|
||||||
"Returns an HttpResponse object for the given HttpRequest"
|
"Returns an HttpResponse object for the given HttpRequest"
|
||||||
|
|
||||||
|
# Setup default url resolver for this thread, this code is outside
|
||||||
|
# the try/except so we don't get a spurious "unbound local
|
||||||
|
# variable" exception in the event an exception is raised before
|
||||||
|
# resolver is set
|
||||||
|
urlconf = settings.ROOT_URLCONF
|
||||||
|
urlresolvers.set_urlconf(urlconf)
|
||||||
|
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
||||||
try:
|
try:
|
||||||
# Setup default url resolver for this thread, this code is outside
|
response = None
|
||||||
# the try/except so we don't get a spurious "unbound local
|
# Apply request middleware
|
||||||
# variable" exception in the event an exception is raised before
|
for middleware_method in self._request_middleware:
|
||||||
# resolver is set
|
response = middleware_method(request)
|
||||||
urlconf = settings.ROOT_URLCONF
|
if response:
|
||||||
urlresolvers.set_urlconf(urlconf)
|
break
|
||||||
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
|
||||||
try:
|
if response is None:
|
||||||
response = None
|
if hasattr(request, 'urlconf'):
|
||||||
# Apply request middleware
|
# Reset url resolver with a custom urlconf.
|
||||||
for middleware_method in self._request_middleware:
|
urlconf = request.urlconf
|
||||||
response = middleware_method(request)
|
urlresolvers.set_urlconf(urlconf)
|
||||||
|
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
||||||
|
|
||||||
|
resolver_match = resolver.resolve(request.path_info)
|
||||||
|
callback, callback_args, callback_kwargs = resolver_match
|
||||||
|
request.resolver_match = resolver_match
|
||||||
|
|
||||||
|
# Apply view middleware
|
||||||
|
for middleware_method in self._view_middleware:
|
||||||
|
response = middleware_method(request, callback, callback_args, callback_kwargs)
|
||||||
if response:
|
if response:
|
||||||
break
|
break
|
||||||
|
|
||||||
if response is None:
|
if response is None:
|
||||||
if hasattr(request, 'urlconf'):
|
wrapped_callback = self.make_view_atomic(callback)
|
||||||
# Reset url resolver with a custom urlconf.
|
try:
|
||||||
urlconf = request.urlconf
|
response = wrapped_callback(request, *callback_args, **callback_kwargs)
|
||||||
urlresolvers.set_urlconf(urlconf)
|
except Exception as e:
|
||||||
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
|
# If the view raised an exception, run it through exception
|
||||||
|
# middleware, and if the exception middleware returns a
|
||||||
resolver_match = resolver.resolve(request.path_info)
|
# response, use that. Otherwise, reraise the exception.
|
||||||
callback, callback_args, callback_kwargs = resolver_match
|
for middleware_method in self._exception_middleware:
|
||||||
request.resolver_match = resolver_match
|
response = middleware_method(request, e)
|
||||||
|
|
||||||
# Apply view middleware
|
|
||||||
for middleware_method in self._view_middleware:
|
|
||||||
response = middleware_method(request, callback, callback_args, callback_kwargs)
|
|
||||||
if response:
|
if response:
|
||||||
break
|
break
|
||||||
|
if response is None:
|
||||||
|
raise
|
||||||
|
|
||||||
if response is None:
|
# Complain if the view returned None (a common error).
|
||||||
wrapped_callback = self.make_view_atomic(callback)
|
if response is None:
|
||||||
try:
|
if isinstance(callback, types.FunctionType): # FBV
|
||||||
response = wrapped_callback(request, *callback_args, **callback_kwargs)
|
view_name = callback.__name__
|
||||||
except Exception as e:
|
else: # CBV
|
||||||
# If the view raised an exception, run it through exception
|
view_name = callback.__class__.__name__ + '.__call__'
|
||||||
# middleware, and if the exception middleware returns a
|
raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
|
||||||
# response, use that. Otherwise, reraise the exception.
|
|
||||||
for middleware_method in self._exception_middleware:
|
|
||||||
response = middleware_method(request, e)
|
|
||||||
if response:
|
|
||||||
break
|
|
||||||
if response is None:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Complain if the view returned None (a common error).
|
# If the response supports deferred rendering, apply template
|
||||||
if response is None:
|
# response middleware and then render the response
|
||||||
if isinstance(callback, types.FunctionType): # FBV
|
if hasattr(response, 'render') and callable(response.render):
|
||||||
view_name = callback.__name__
|
for middleware_method in self._template_response_middleware:
|
||||||
else: # CBV
|
response = middleware_method(request, response)
|
||||||
view_name = callback.__class__.__name__ + '.__call__'
|
response = response.render()
|
||||||
raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
|
|
||||||
|
|
||||||
# If the response supports deferred rendering, apply template
|
except http.Http404 as e:
|
||||||
# response middleware and then render the response
|
logger.warning('Not Found: %s', request.path,
|
||||||
if hasattr(response, 'render') and callable(response.render):
|
extra={
|
||||||
for middleware_method in self._template_response_middleware:
|
'status_code': 404,
|
||||||
response = middleware_method(request, response)
|
'request': request
|
||||||
response = response.render()
|
})
|
||||||
|
if settings.DEBUG:
|
||||||
except http.Http404 as e:
|
response = debug.technical_404_response(request, e)
|
||||||
logger.warning('Not Found: %s', request.path,
|
else:
|
||||||
extra={
|
|
||||||
'status_code': 404,
|
|
||||||
'request': request
|
|
||||||
})
|
|
||||||
if settings.DEBUG:
|
|
||||||
response = debug.technical_404_response(request, e)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
callback, param_dict = resolver.resolve404()
|
|
||||||
response = callback(request, **param_dict)
|
|
||||||
except:
|
|
||||||
signals.got_request_exception.send(sender=self.__class__, request=request)
|
|
||||||
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
|
||||||
except PermissionDenied:
|
|
||||||
logger.warning(
|
|
||||||
'Forbidden (Permission denied): %s', request.path,
|
|
||||||
extra={
|
|
||||||
'status_code': 403,
|
|
||||||
'request': request
|
|
||||||
})
|
|
||||||
try:
|
try:
|
||||||
callback, param_dict = resolver.resolve403()
|
callback, param_dict = resolver.resolve404()
|
||||||
response = callback(request, **param_dict)
|
response = callback(request, **param_dict)
|
||||||
except:
|
except:
|
||||||
signals.got_request_exception.send(
|
signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||||
sender=self.__class__, request=request)
|
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||||
response = self.handle_uncaught_exception(request,
|
|
||||||
resolver, sys.exc_info())
|
except PermissionDenied:
|
||||||
except SystemExit:
|
logger.warning(
|
||||||
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
'Forbidden (Permission denied): %s', request.path,
|
||||||
raise
|
extra={
|
||||||
except: # Handle everything else, including SuspiciousOperation, etc.
|
'status_code': 403,
|
||||||
# Get the exception info now, in case another exception is thrown later.
|
'request': request
|
||||||
signals.got_request_exception.send(sender=self.__class__, request=request)
|
})
|
||||||
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
try:
|
||||||
finally:
|
callback, param_dict = resolver.resolve403()
|
||||||
# Reset URLconf for this thread on the way out for complete
|
response = callback(request, **param_dict)
|
||||||
# isolation of request.urlconf
|
except:
|
||||||
urlresolvers.set_urlconf(None)
|
signals.got_request_exception.send(
|
||||||
|
sender=self.__class__, request=request)
|
||||||
|
response = self.handle_uncaught_exception(request,
|
||||||
|
resolver, sys.exc_info())
|
||||||
|
|
||||||
|
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.
|
||||||
|
signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||||
|
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Apply response middleware, regardless of the response
|
# Apply response middleware, regardless of the response
|
||||||
|
|
|
@ -26,7 +26,7 @@ from django.core.management import call_command
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
|
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
|
||||||
WSGIServerException)
|
WSGIServerException)
|
||||||
from django.core.urlresolvers import clear_url_caches
|
from django.core.urlresolvers import clear_url_caches, set_urlconf
|
||||||
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
|
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
|
||||||
from django.forms.fields import CharField
|
from django.forms.fields import CharField
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
|
@ -497,6 +497,7 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
**{'verbosity': 0, 'database': db_name, 'skip_validation': True})
|
**{'verbosity': 0, 'database': db_name, 'skip_validation': True})
|
||||||
|
|
||||||
def _urlconf_setup(self):
|
def _urlconf_setup(self):
|
||||||
|
set_urlconf(None)
|
||||||
if hasattr(self, 'urls'):
|
if hasattr(self, 'urls'):
|
||||||
self._old_root_urlconf = settings.ROOT_URLCONF
|
self._old_root_urlconf = settings.ROOT_URLCONF
|
||||||
settings.ROOT_URLCONF = self.urls
|
settings.ROOT_URLCONF = self.urls
|
||||||
|
@ -527,6 +528,7 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
skip_validation=True, reset_sequences=False)
|
skip_validation=True, reset_sequences=False)
|
||||||
|
|
||||||
def _urlconf_teardown(self):
|
def _urlconf_teardown(self):
|
||||||
|
set_urlconf(None)
|
||||||
if hasattr(self, '_old_root_urlconf'):
|
if hasattr(self, '_old_root_urlconf'):
|
||||||
settings.ROOT_URLCONF = self._old_root_urlconf
|
settings.ROOT_URLCONF = self._old_root_urlconf
|
||||||
clear_url_caches()
|
clear_url_caches()
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.http import HttpResponse, StreamingHttpResponse
|
||||||
|
|
||||||
from . import urlconf_inner
|
from . import urlconf_inner
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,3 +13,23 @@ class ChangeURLconfMiddleware(object):
|
||||||
class NullChangeURLconfMiddleware(object):
|
class NullChangeURLconfMiddleware(object):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
request.urlconf = None
|
request.urlconf = None
|
||||||
|
|
||||||
|
class ReverseInnerInResponseMiddleware(object):
|
||||||
|
def process_response(self, *args, **kwargs):
|
||||||
|
return HttpResponse(reverse('inner'))
|
||||||
|
|
||||||
|
class ReverseOuterInResponseMiddleware(object):
|
||||||
|
def process_response(self, *args, **kwargs):
|
||||||
|
return HttpResponse(reverse('outer'))
|
||||||
|
|
||||||
|
class ReverseInnerInStreaming(object):
|
||||||
|
def process_view(self, *args, **kwargs):
|
||||||
|
def stream():
|
||||||
|
yield reverse('inner')
|
||||||
|
return StreamingHttpResponse(stream())
|
||||||
|
|
||||||
|
class ReverseOuterInStreaming(object):
|
||||||
|
def process_view(self, *args, **kwargs):
|
||||||
|
def stream():
|
||||||
|
yield reverse('outer')
|
||||||
|
return StreamingHttpResponse(stream())
|
||||||
|
|
|
@ -462,6 +462,59 @@ class RequestURLconfTests(TestCase):
|
||||||
)
|
)
|
||||||
self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
|
self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
|
||||||
|
|
||||||
|
def test_reverse_inner_in_response_middleware(self):
|
||||||
|
"""
|
||||||
|
Test reversing an URL from the *overridden* URLconf from inside
|
||||||
|
a response middleware.
|
||||||
|
"""
|
||||||
|
settings.MIDDLEWARE_CLASSES += (
|
||||||
|
'%s.ChangeURLconfMiddleware' % middleware.__name__,
|
||||||
|
'%s.ReverseInnerInResponseMiddleware' % middleware.__name__,
|
||||||
|
)
|
||||||
|
response = self.client.get('/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.content, b'/second_test/')
|
||||||
|
|
||||||
|
def test_reverse_outer_in_response_middleware(self):
|
||||||
|
"""
|
||||||
|
Test reversing an URL from the *default* URLconf from inside
|
||||||
|
a response middleware.
|
||||||
|
"""
|
||||||
|
settings.MIDDLEWARE_CLASSES += (
|
||||||
|
'%s.ChangeURLconfMiddleware' % middleware.__name__,
|
||||||
|
'%s.ReverseOuterInResponseMiddleware' % middleware.__name__,
|
||||||
|
)
|
||||||
|
message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found."
|
||||||
|
with self.assertRaisesMessage(NoReverseMatch, message):
|
||||||
|
self.client.get('/second_test/')
|
||||||
|
|
||||||
|
def test_reverse_inner_in_streaming(self):
|
||||||
|
"""
|
||||||
|
Test reversing an URL from the *overridden* URLconf from inside
|
||||||
|
a streaming response.
|
||||||
|
"""
|
||||||
|
settings.MIDDLEWARE_CLASSES += (
|
||||||
|
'%s.ChangeURLconfMiddleware' % middleware.__name__,
|
||||||
|
'%s.ReverseInnerInStreaming' % middleware.__name__,
|
||||||
|
)
|
||||||
|
response = self.client.get('/second_test/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(b''.join(response), b'/second_test/')
|
||||||
|
|
||||||
|
def test_reverse_outer_in_streaming(self):
|
||||||
|
"""
|
||||||
|
Test reversing an URL from the *default* URLconf from inside
|
||||||
|
a streaming response.
|
||||||
|
"""
|
||||||
|
settings.MIDDLEWARE_CLASSES += (
|
||||||
|
'%s.ChangeURLconfMiddleware' % middleware.__name__,
|
||||||
|
'%s.ReverseOuterInStreaming' % middleware.__name__,
|
||||||
|
)
|
||||||
|
message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found."
|
||||||
|
with self.assertRaisesMessage(NoReverseMatch, message):
|
||||||
|
self.client.get('/second_test/')
|
||||||
|
b''.join(self.client.get('/second_test/'))
|
||||||
|
|
||||||
class ErrorHandlerResolutionTests(TestCase):
|
class ErrorHandlerResolutionTests(TestCase):
|
||||||
"""Tests for handler404 and handler500"""
|
"""Tests for handler404 and handler500"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue