Ticket #6094: 6094.3.diff

File 6094.3.diff, 22.6 KB (added by Gary Wilson, 16 years ago)
  • tests/regressiontests/handler_exceptions/models.py

    === added directory 'tests/regressiontests/handler_exceptions'
    === added file 'tests/regressiontests/handler_exceptions/__init__.py'
    === added file 'tests/regressiontests/handler_exceptions/models.py'
     
     1# models file for tests to run.
  • tests/regressiontests/handler_exceptions/tests.py

    === added file 'tests/regressiontests/handler_exceptions/tests.py'
     
     1from django.test import TestCase
     2from django.test.client import Client, ClientHandler
     3
     4class GoodMiddleware(object):
     5    def process_request(self, *args, **kwargs):
     6        pass
     7    def process_view(self, *args, **kwargs):
     8        pass
     9    def process_exception(self, *args, **kwargs):
     10        pass
     11    def process_response(self, request, response):
     12        return response
     13
     14
     15class MiddlewareException(Exception): pass
     16class RequestMiddlewareException(MiddlewareException): pass
     17class ViewMiddlewareException(MiddlewareException): pass
     18class ExceptionMiddlewareException(MiddlewareException): pass
     19class ResponseMiddlewareException(MiddlewareException): pass
     20
     21
     22class BadRequestMiddleware(object):
     23    def process_request(self, *args, **kwargs):
     24        raise RequestMiddlewareException
     25
     26class BadViewMiddleware(object):
     27    def process_view(self, *args, **kwargs):
     28        raise ViewMiddlewareException
     29
     30class BadExceptionMiddleware(object):
     31    def process_exception(self, *args, **kwargs):
     32        raise ExceptionMiddlewareException
     33
     34class BadResponseMiddleware(object):
     35    def process_response(self, *args, **kwargs):
     36        raise ResponseMiddlewareException
     37
     38
     39class MiddlewareResettingClientHandler(ClientHandler):
     40    """
     41    A handler that clears loaded middleware after each request.
     42    """
     43    def __call__(self, *args, **kwargs):
     44        response = super(MiddlewareResettingClientHandler, self).__call__(*args, **kwargs)
     45        # ClientHandler's __call__ method will load middleware if
     46        # self._request_middleware is None.  We set it to None here so that
     47        # middleware will be reloaded next request.
     48        self._request_middleware = None
     49        return response
     50
     51
     52class HandlerClient(Client):
     53    """
     54    A custom Client that doesn't reraise request-handling exceptions and uses
     55    the MiddlewareResettingClientHandler.
     56    """
     57    def __init__(self, *args, **kwargs):
     58        super(HandlerClient, self).__init__(*args, **kwargs)
     59        self.reraise_exceptions = False
     60        self.handler = MiddlewareResettingClientHandler()
     61
     62
     63class HandlerExceptionTests(TestCase):
     64    def setUp(self):
     65        self.client = HandlerClient()
     66        self.default_middleware = None
     67        # Combinations that will raise errors.
     68        self.bad_combinations = (
     69            # Middleware, request path
     70            ('BadRequestMiddleware', '/good_view/', RequestMiddlewareException),
     71            ('BadViewMiddleware', '/good_view/', ViewMiddlewareException),
     72            ('BadResponseMiddleware', '/good_view/', ResponseMiddlewareException),
     73            ('BadRequestMiddleware', '/bad_view/', RequestMiddlewareException),
     74            ('BadViewMiddleware', '/bad_view/', ViewMiddlewareException),
     75            ('BadExceptionMiddleware', '/bad_view/', ExceptionMiddlewareException),
     76            ('BadResponseMiddleware', '/bad_view/', ResponseMiddlewareException),
     77        )
     78        # Combinations that will not raise errors.
     79        self.good_combinations = (
     80            ('GoodMiddleware', '/good_view/'),
     81            # Exception middleware doesn't get run if the view doesn't raise
     82            # an exception.
     83            ('BadExceptionMiddleware', '/good_view/'),
     84        )
     85
     86    def setup_middleware(self, middleware):
     87        """
     88        Append middleware to list of installed middleware.
     89        """
     90        from django.conf import settings
     91        # If we haven't saved the default middleware, do so now.
     92        if self.default_middleware is None:
     93            self.default_middleware = settings.MIDDLEWARE_CLASSES
     94        middleware_path = 'regressiontests.handler_exceptions.tests.' + middleware
     95        settings.MIDDLEWARE_CLASSES += (middleware_path,)
     96
     97    def reset_middleware(self):
     98        """
     99        Restores middleware to the default installed middleware.
     100        """
     101        from django.conf import settings
     102        settings.MIDDLEWARE_CLASSES = self.default_middleware
     103
     104    def test_exception_handling(self):
     105        """
     106        Tests that exceptions raised in middleware and views are properly
     107        caught and render error templates.
     108        """
     109        # Just for good measure, check that the good combination doesn't raise
     110        # an error.
     111        for middleware, path in self.good_combinations:
     112            self.setup_middleware(middleware)
     113            response = self.client.get('/handler_exceptions' + path)
     114            self.assertContains(response, 'good view')
     115            self.reset_middleware()
     116        # Now test that all the bad combinations render the 500 template.
     117        for middleware, path, exception in self.bad_combinations:
     118            self.setup_middleware(middleware)
     119            response = self.client.get('/handler_exceptions' + path)
     120            self.assertContains(response, '500 Error', status_code=500)
     121            self.assertTemplateUsed(response, '500.html')
     122            self.reset_middleware()
     123
     124    def test_exception_raising(self):
     125        """
     126        Tests that the correct exceptions bubble up from middleware with errors.
     127        """
     128        # Turn on client's reraising of exceptions for this test.
     129        self.client.reraise_exceptions = True
     130        for middleware, path, exception in self.bad_combinations:
     131            self.setup_middleware(middleware)
     132            fail = False
     133            try:
     134                self.client.get('/handler_exceptions' + path)
     135                # If above didn't raise an exception, then test failed.
     136                fail = True
     137            except exception:
     138                # We got the exception we were looking for, test passes.
     139                pass
     140            except Exception:
     141                # We got an exception that we wore not expecting.
     142                from sys import exc_info
     143                unexpected_exception = exc_info()[0]
     144                self.fail("Expected %s to be raised, but got %s"
     145                          % (exception, unexpected_exception))
     146            if fail:
     147                self.fail("Expected %s to be raised.  Middleware: %s, Path: %s"
     148                          % (exception, middleware, path))
     149            self.reset_middleware()
  • tests/regressiontests/handler_exceptions/urls.py

    === added file 'tests/regressiontests/handler_exceptions/urls.py'
     
     1from django.conf.urls.defaults import *
     2
     3import views
     4
     5urlpatterns = patterns('',
     6    (r'^good_view/$', views.good_view),
     7    (r'^bad_view/$', views.bad_view),
     8)
  • tests/regressiontests/handler_exceptions/views.py

    === added file 'tests/regressiontests/handler_exceptions/views.py'
     
     1from django.http import HttpResponse
     2
     3def good_view(request):
     4    return HttpResponse("good view")
     5
     6def bad_view(request):
     7    raise Exception("View raised exception")
  • django/core/handlers/base.py

    === modified file 'django/core/handlers/base.py'
     
    33from django import http
    44from django.core import signals
    55from django.dispatch import dispatcher
     6from django.core.urlresolvers import RegexURLResolver
     7
     8
     9def resolver(request):
     10    """
     11    Returns a RegexURLResolver for the request's urlconf.
     12
     13    If the request does not have a urlconf object, then the default of
     14    settings.ROOT_URLCONF is used.
     15    """
     16    from django.conf import settings
     17    urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
     18    return RegexURLResolver(r'^/', urlconf)
     19
    620
    721class BaseHandler(object):
    822    # Changes that are always applied to a response (in this order).
     
    5367            if hasattr(mw_instance, 'process_exception'):
    5468                self._exception_middleware.insert(0, mw_instance.process_exception)
    5569
    56     def get_response(self, request):
    57         "Returns an HttpResponse object for the given HttpRequest"
    58         from django.core import exceptions, urlresolvers
    59         from django.core.mail import mail_admins
    60         from django.conf import settings
    61 
    62         # Apply request middleware
     70    def handle_request(self, request):
     71        """
     72        Takes a request and returns a response.
     73
     74        Processing occurs according to the following diagram:
     75
     76            Request                    Response
     77               |                           |
     78          +----------+                +----------+
     79          | Request  |                | Response |<--------+
     80          |Middleware|---Response?--->|Middleware|         |
     81          +----------+                +----------+   404/500 Response
     82               |                           |               |
     83        ---------Request Exception Handler-----------------+---------
     84               |                           |
     85          +---------+                      +<-------+
     86          | URLConf |                      |        |
     87          +---------+                      |   +----------+
     88               |                           |   |Exception |
     89          +----------+                     |   |Middleware|
     90          |   View   |                     |   +----------+
     91          |Middleware|-----Response?------>+        |
     92          +----------+                     |    Exception
     93               |                           |        |
     94        ----------View Exception Handler------------+----------------
     95               |                           |
     96            +------+                       |
     97            | View |-----Response--------->+
     98            +------+
     99        """
     100        dispatcher.send(signal=signals.request_started)
     101        try:
     102            try:
     103                request = self.request_class(request)
     104            except UnicodeDecodeError:
     105                response = http.HttpResponseBadRequest()
     106            else:
     107                # See if the request middleware returns a response.
     108                response = self.get_response(self.apply_request_middleware,
     109                                             request)
     110                # If not, then call the view.
     111                if not response:
     112                    response = self.get_response(self.run_view_processing,
     113                                                 request)
     114            # All responses go through the response middleware.
     115            response = self.get_response(self.apply_response_middleware,
     116                                         request, response)
     117            response = self.apply_response_fixes(request, response)
     118            return response
     119        finally:
     120            dispatcher.send(signal=signals.request_finished)
     121
     122    def apply_request_middleware(self, request):
     123        """
     124        Applies request middleware one at a time until a response is returned.
     125        If none of request middleware returns a response, then return None.
     126        """
    63127        for middleware_method in self._request_middleware:
    64128            response = middleware_method(request)
    65129            if response:
    66130                return response
    67131
    68         # Get urlconf from request object, if available.  Otherwise use default.
    69         urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
    70 
    71         resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
     132    def run_view_processing(self, request):
     133        """
     134        Apply view middleware and call view.
     135        """
     136        callback, callback_args, callback_kwargs = resolver(request).resolve(request.path)
     137
     138        # Apply view middleware
     139        for middleware_method in self._view_middleware:
     140            response = middleware_method(request, callback, callback_args, callback_kwargs)
     141            if response:
     142                return response
     143
    72144        try:
    73             callback, callback_args, callback_kwargs = resolver.resolve(request.path)
    74 
    75             # Apply view middleware
    76             for middleware_method in self._view_middleware:
    77                 response = middleware_method(request, callback, callback_args, callback_kwargs)
     145            response = callback(request, *callback_args, **callback_kwargs)
     146        except Exception, e:
     147            # If the view raised an exception, run it through exception
     148            # middleware, and if the exception middleware returns a
     149            # response, use that. Otherwise, reraise the exception.
     150            for middleware_method in self._exception_middleware:
     151                response = middleware_method(request, e)
    78152                if response:
    79153                    return response
     154            raise
    80155
     156        # Complain if the view returned None (a common error).
     157        if response is None:
    81158            try:
    82                 response = callback(request, *callback_args, **callback_kwargs)
    83             except Exception, e:
    84                 # If the view raised an exception, run it through exception
    85                 # middleware, and if the exception middleware returns a
    86                 # response, use that. Otherwise, reraise the exception.
    87                 for middleware_method in self._exception_middleware:
    88                     response = middleware_method(request, e)
    89                     if response:
    90                         return response
    91                 raise
    92 
    93             # Complain if the view returned None (a common error).
     159                view_name = callback.func_name # If it's a function
     160            except AttributeError:
     161                view_name = callback.__class__.__name__ + '.__call__' # If it's a class
     162            raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
     163
     164        return response
     165
     166    def apply_response_middleware(self, request, response):
     167        """
     168        Applies all response middleware to the response.
     169        """
     170        for middleware_method in self._response_middleware:
     171            response = middleware_method(request, response)
     172        return response
     173
     174    def get_response(self, method, request, response=None):
     175        """
     176        Returns an HttpResponse by calling the passed method with the given
     177        HttpRequest object.  If an HttpResponse is passed, it will also be
     178        passed in the call to method.  Exceptions raised by calling method
     179        will be handled like so:
     180
     181        * Http404 exception - If settings.DEBUG is True, the
     182          technical_404_response debug view will be called.  If settings.DEBUG
     183          is False, the URL resolver's handler404 view will be called.
     184        * PermissionDenied exception - return HttpResponseForbidden.
     185        * SystemExit exception - ignored.
     186        * All other exceptions - If settings.DEBUG is True, the
     187          technical_500_response debug view will be called.  If settings.DEBUG
     188          is False, the URL resolver's handler500 view will be called.
     189        """
     190        from django.core import exceptions
     191        from django.core.mail import mail_admins
     192        from django.conf import settings
     193
     194        try:
    94195            if response is None:
    95                 try:
    96                     view_name = callback.func_name # If it's a function
    97                 except AttributeError:
    98                     view_name = callback.__class__.__name__ + '.__call__' # If it's a class
    99                 raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
    100 
    101             return response
     196                return method(request)
     197            else:
     198                return method(request, response)
    102199        except http.Http404, e:
    103200            if settings.DEBUG:
    104201                from django.views import debug
    105202                return debug.technical_404_response(request, e)
    106203            else:
    107                 callback, param_dict = resolver.resolve404()
     204                callback, param_dict = resolver(request).resolve404()
    108205                return callback(request, **param_dict)
    109206        except exceptions.PermissionDenied:
    110207            return http.HttpResponseForbidden('<h1>Permission denied</h1>')
     
    127224                message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
    128225                mail_admins(subject, message, fail_silently=True)
    129226                # Return an HttpResponse that displays a friendly error message.
    130                 callback, param_dict = resolver.resolve500()
     227                callback, param_dict = resolver(request).resolve500()
    131228                return callback(request, **param_dict)
    132229
    133230    def _get_traceback(self, exc_info=None):
     
    144241        for func in self.response_fixes:
    145242            response = func(request, response)
    146243        return response
    147 
  • django/core/handlers/modpython.py

    === modified file 'django/core/handlers/modpython.py'
     
    22from pprint import pformat
    33
    44from django import http
    5 from django.core import signals
    65from django.core.handlers.base import BaseHandler
    7 from django.dispatch import dispatcher
    86from django.utils import datastructures
    97from django.utils.encoding import force_unicode
    108
     
    151149        if self._request_middleware is None:
    152150            self.load_middleware()
    153151
    154         dispatcher.send(signal=signals.request_started)
    155         try:
    156             try:
    157                 request = self.request_class(req)
    158             except UnicodeDecodeError:
    159                 response = http.HttpResponseBadRequest()
    160             else:
    161                 response = self.get_response(request)
    162 
    163                 # Apply response middleware
    164                 for middleware_method in self._response_middleware:
    165                     response = middleware_method(request, response)
    166                 response = self.apply_response_fixes(request, response)
    167         finally:
    168             dispatcher.send(signal=signals.request_finished)
     152        response = self.handle_request(req)
    169153
    170154        # Convert our custom HttpResponse object back into the mod_python req.
    171155        req.content_type = response['Content-Type']
  • django/core/handlers/wsgi.py

    === modified file 'django/core/handlers/wsgi.py'
     
    66    from StringIO import StringIO
    77
    88from django import http
    9 from django.core import signals
    109from django.core.handlers.base import BaseHandler
    11 from django.dispatch import dispatcher
    1210from django.utils import datastructures
    1311from django.utils.encoding import force_unicode
    1412
     
    195193                self.load_middleware()
    196194            self.initLock.release()
    197195
    198         dispatcher.send(signal=signals.request_started)
    199         try:
    200             try:
    201                 request = self.request_class(environ)
    202             except UnicodeDecodeError:
    203                 response = http.HttpResponseBadRequest()
    204             else:
    205                 response = self.get_response(request)
    206 
    207                 # Apply response middleware
    208                 for middleware_method in self._response_middleware:
    209                     response = middleware_method(request, response)
    210                 response = self.apply_response_fixes(request, response)
    211         finally:
    212             dispatcher.send(signal=signals.request_finished)
     196        response = self.handle_request(environ)
    213197
    214198        try:
    215199            status_text = STATUS_CODE_TEXT[response.status_code]
  • django/test/client.py

    === modified file 'django/test/client.py'
     
    2525    Uses the WSGI interface to compose requests, but returns
    2626    the raw HttpResponse object
    2727    """
     28    request_class = WSGIRequest
     29
    2830    def __call__(self, environ):
    2931        from django.conf import settings
    30         from django.core import signals
    3132
    3233        # Set up middleware if needed. We couldn't do this earlier, because
    3334        # settings weren't available.
    3435        if self._request_middleware is None:
    3536            self.load_middleware()
    3637
    37         dispatcher.send(signal=signals.request_started)
    38         try:
    39             request = WSGIRequest(environ)
    40             response = self.get_response(request)
    41 
    42             # Apply response middleware
    43             for middleware_method in self._response_middleware:
    44                 response = middleware_method(request, response)
    45             response = self.apply_response_fixes(request, response)
    46         finally:
    47             dispatcher.send(signal=signals.request_finished)
    48 
    49         return response
     38        return self.handle_request(environ)
    5039
    5140def store_rendered_templates(store, signal, sender, template, context):
    5241    "A utility function for storing templates and contexts that are rendered"
     
    9685    ])
    9786    return '\r\n'.join(lines)
    9887
    99 class Client:
     88class Client(object):
    10089    """
    10190    A class that can act as a client for testing purposes.
    10291
     
    119108        self.defaults = defaults
    120109        self.cookies = SimpleCookie()
    121110        self.exc_info = None
     111        self.reraise_exceptions = True
    122112
    123113    def store_exc_info(self, *args, **kwargs):
    124114        """
     
    180170                raise
    181171
    182172        # Look for a signalled exception and reraise it
    183         if self.exc_info:
     173        if self.exc_info and self.reraise_exceptions:
    184174            raise self.exc_info[1], None, self.exc_info[2]
    185175
    186176        # Save the client and request that stimulated the response
     
    262252            self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
    263253
    264254            # Save the session values
    265             request.session.save()   
     255            request.session.save()
    266256
    267257            return True
    268258        else:
  • tests/urls.py

    === modified file 'tests/urls.py'
     
    1111
    1212    # test urlconf for {% url %} template tag
    1313    (r'^url_tag/', include('regressiontests.templates.urls')),
    14    
     14
    1515    # django built-in views
    1616    (r'^views/', include('regressiontests.views.urls')),
    1717
    1818    # test urlconf for middleware tests
    1919    (r'^middleware/', include('regressiontests.middleware.urls')),
     20   
     21    # urlconf for handler_exceptions tests.
     22    (r'^handler_exceptions/', include('regressiontests.handler_exceptions.urls')),
    2023)
Back to Top