Code

Ticket #6094: 6094.3.diff

File 6094.3.diff, 22.6 KB (added by gwilson, 6 years ago)
Line 
1=== added directory 'tests/regressiontests/handler_exceptions'
2=== added file 'tests/regressiontests/handler_exceptions/__init__.py'
3=== added file 'tests/regressiontests/handler_exceptions/models.py'
4--- tests/regressiontests/handler_exceptions/models.py  1970-01-01 00:00:00 +0000
5+++ tests/regressiontests/handler_exceptions/models.py  2007-12-02 19:40:43 +0000
6@@ -0,0 +1,1 @@
7+# models file for tests to run.
8
9=== added file 'tests/regressiontests/handler_exceptions/tests.py'
10--- tests/regressiontests/handler_exceptions/tests.py   1970-01-01 00:00:00 +0000
11+++ tests/regressiontests/handler_exceptions/tests.py   2007-12-02 21:25:51 +0000
12@@ -0,0 +1,149 @@
13+from django.test import TestCase
14+from django.test.client import Client, ClientHandler
15+
16+class GoodMiddleware(object):
17+    def process_request(self, *args, **kwargs):
18+        pass
19+    def process_view(self, *args, **kwargs):
20+        pass
21+    def process_exception(self, *args, **kwargs):
22+        pass
23+    def process_response(self, request, response):
24+        return response
25+
26+
27+class MiddlewareException(Exception): pass
28+class RequestMiddlewareException(MiddlewareException): pass
29+class ViewMiddlewareException(MiddlewareException): pass
30+class ExceptionMiddlewareException(MiddlewareException): pass
31+class ResponseMiddlewareException(MiddlewareException): pass
32+
33+
34+class BadRequestMiddleware(object):
35+    def process_request(self, *args, **kwargs):
36+        raise RequestMiddlewareException
37+
38+class BadViewMiddleware(object):
39+    def process_view(self, *args, **kwargs):
40+        raise ViewMiddlewareException
41+
42+class BadExceptionMiddleware(object):
43+    def process_exception(self, *args, **kwargs):
44+        raise ExceptionMiddlewareException
45+
46+class BadResponseMiddleware(object):
47+    def process_response(self, *args, **kwargs):
48+        raise ResponseMiddlewareException
49+
50+
51+class MiddlewareResettingClientHandler(ClientHandler):
52+    """
53+    A handler that clears loaded middleware after each request.
54+    """
55+    def __call__(self, *args, **kwargs):
56+        response = super(MiddlewareResettingClientHandler, self).__call__(*args, **kwargs)
57+        # ClientHandler's __call__ method will load middleware if
58+        # self._request_middleware is None.  We set it to None here so that
59+        # middleware will be reloaded next request.
60+        self._request_middleware = None
61+        return response
62+
63+
64+class HandlerClient(Client):
65+    """
66+    A custom Client that doesn't reraise request-handling exceptions and uses
67+    the MiddlewareResettingClientHandler.
68+    """
69+    def __init__(self, *args, **kwargs):
70+        super(HandlerClient, self).__init__(*args, **kwargs)
71+        self.reraise_exceptions = False
72+        self.handler = MiddlewareResettingClientHandler()
73+
74+
75+class HandlerExceptionTests(TestCase):
76+    def setUp(self):
77+        self.client = HandlerClient()
78+        self.default_middleware = None
79+        # Combinations that will raise errors.
80+        self.bad_combinations = (
81+            # Middleware, request path
82+            ('BadRequestMiddleware', '/good_view/', RequestMiddlewareException),
83+            ('BadViewMiddleware', '/good_view/', ViewMiddlewareException),
84+            ('BadResponseMiddleware', '/good_view/', ResponseMiddlewareException),
85+            ('BadRequestMiddleware', '/bad_view/', RequestMiddlewareException),
86+            ('BadViewMiddleware', '/bad_view/', ViewMiddlewareException),
87+            ('BadExceptionMiddleware', '/bad_view/', ExceptionMiddlewareException),
88+            ('BadResponseMiddleware', '/bad_view/', ResponseMiddlewareException),
89+        )
90+        # Combinations that will not raise errors.
91+        self.good_combinations = (
92+            ('GoodMiddleware', '/good_view/'),
93+            # Exception middleware doesn't get run if the view doesn't raise
94+            # an exception.
95+            ('BadExceptionMiddleware', '/good_view/'),
96+        )
97+
98+    def setup_middleware(self, middleware):
99+        """
100+        Append middleware to list of installed middleware.
101+        """
102+        from django.conf import settings
103+        # If we haven't saved the default middleware, do so now.
104+        if self.default_middleware is None:
105+            self.default_middleware = settings.MIDDLEWARE_CLASSES
106+        middleware_path = 'regressiontests.handler_exceptions.tests.' + middleware
107+        settings.MIDDLEWARE_CLASSES += (middleware_path,)
108+
109+    def reset_middleware(self):
110+        """
111+        Restores middleware to the default installed middleware.
112+        """
113+        from django.conf import settings
114+        settings.MIDDLEWARE_CLASSES = self.default_middleware
115+
116+    def test_exception_handling(self):
117+        """
118+        Tests that exceptions raised in middleware and views are properly
119+        caught and render error templates.
120+        """
121+        # Just for good measure, check that the good combination doesn't raise
122+        # an error.
123+        for middleware, path in self.good_combinations:
124+            self.setup_middleware(middleware)
125+            response = self.client.get('/handler_exceptions' + path)
126+            self.assertContains(response, 'good view')
127+            self.reset_middleware()
128+        # Now test that all the bad combinations render the 500 template.
129+        for middleware, path, exception in self.bad_combinations:
130+            self.setup_middleware(middleware)
131+            response = self.client.get('/handler_exceptions' + path)
132+            self.assertContains(response, '500 Error', status_code=500)
133+            self.assertTemplateUsed(response, '500.html')
134+            self.reset_middleware()
135+
136+    def test_exception_raising(self):
137+        """
138+        Tests that the correct exceptions bubble up from middleware with errors.
139+        """
140+        # Turn on client's reraising of exceptions for this test.
141+        self.client.reraise_exceptions = True
142+        for middleware, path, exception in self.bad_combinations:
143+            self.setup_middleware(middleware)
144+            fail = False
145+            try:
146+                self.client.get('/handler_exceptions' + path)
147+                # If above didn't raise an exception, then test failed.
148+                fail = True
149+            except exception:
150+                # We got the exception we were looking for, test passes.
151+                pass
152+            except Exception:
153+                # We got an exception that we wore not expecting.
154+                from sys import exc_info
155+                unexpected_exception = exc_info()[0]
156+                self.fail("Expected %s to be raised, but got %s"
157+                          % (exception, unexpected_exception))
158+            if fail:
159+                self.fail("Expected %s to be raised.  Middleware: %s, Path: %s"
160+                          % (exception, middleware, path))
161+            self.reset_middleware()
162
163=== added file 'tests/regressiontests/handler_exceptions/urls.py'
164--- tests/regressiontests/handler_exceptions/urls.py    1970-01-01 00:00:00 +0000
165+++ tests/regressiontests/handler_exceptions/urls.py    2007-12-02 17:17:21 +0000
166@@ -0,0 +1,8 @@
167+from django.conf.urls.defaults import *
168+
169+import views
170+
171+urlpatterns = patterns('',
172+    (r'^good_view/$', views.good_view),
173+    (r'^bad_view/$', views.bad_view),
174+)
175
176=== added file 'tests/regressiontests/handler_exceptions/views.py'
177--- tests/regressiontests/handler_exceptions/views.py   1970-01-01 00:00:00 +0000
178+++ tests/regressiontests/handler_exceptions/views.py   2007-12-02 18:59:43 +0000
179@@ -0,0 +1,7 @@
180+from django.http import HttpResponse
181+
182+def good_view(request):
183+    return HttpResponse("good view")
184+
185+def bad_view(request):
186+    raise Exception("View raised exception")
187
188=== modified file 'django/core/handlers/base.py'
189--- django/core/handlers/base.py        2007-12-02 02:22:19 +0000
190+++ django/core/handlers/base.py        2008-01-06 21:14:49 +0000
191@@ -3,6 +3,20 @@
192 from django import http
193 from django.core import signals
194 from django.dispatch import dispatcher
195+from django.core.urlresolvers import RegexURLResolver
196+
197+
198+def resolver(request):
199+    """
200+    Returns a RegexURLResolver for the request's urlconf.
201+
202+    If the request does not have a urlconf object, then the default of
203+    settings.ROOT_URLCONF is used.
204+    """
205+    from django.conf import settings
206+    urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
207+    return RegexURLResolver(r'^/', urlconf)
208+
209 
210 class BaseHandler(object):
211     # Changes that are always applied to a response (in this order).
212@@ -53,58 +67,141 @@
213             if hasattr(mw_instance, 'process_exception'):
214                 self._exception_middleware.insert(0, mw_instance.process_exception)
215 
216-    def get_response(self, request):
217-        "Returns an HttpResponse object for the given HttpRequest"
218-        from django.core import exceptions, urlresolvers
219-        from django.core.mail import mail_admins
220-        from django.conf import settings
221-
222-        # Apply request middleware
223+    def handle_request(self, request):
224+        """
225+        Takes a request and returns a response.
226+
227+        Processing occurs according to the following diagram:
228+
229+            Request                    Response
230+               |                           |
231+          +----------+                +----------+
232+          | Request  |                | Response |<--------+
233+          |Middleware|---Response?--->|Middleware|         |
234+          +----------+                +----------+   404/500 Response
235+               |                           |               |
236+        ---------Request Exception Handler-----------------+---------
237+               |                           |
238+          +---------+                      +<-------+
239+          | URLConf |                      |        |
240+          +---------+                      |   +----------+
241+               |                           |   |Exception |
242+          +----------+                     |   |Middleware|
243+          |   View   |                     |   +----------+
244+          |Middleware|-----Response?------>+        |
245+          +----------+                     |    Exception
246+               |                           |        |
247+        ----------View Exception Handler------------+----------------
248+               |                           |
249+            +------+                       |
250+            | View |-----Response--------->+
251+            +------+
252+        """
253+        dispatcher.send(signal=signals.request_started)
254+        try:
255+            try:
256+                request = self.request_class(request)
257+            except UnicodeDecodeError:
258+                response = http.HttpResponseBadRequest()
259+            else:
260+                # See if the request middleware returns a response.
261+                response = self.get_response(self.apply_request_middleware,
262+                                             request)
263+                # If not, then call the view.
264+                if not response:
265+                    response = self.get_response(self.run_view_processing,
266+                                                 request)
267+            # All responses go through the response middleware.
268+            response = self.get_response(self.apply_response_middleware,
269+                                         request, response)
270+            response = self.apply_response_fixes(request, response)
271+            return response
272+        finally:
273+            dispatcher.send(signal=signals.request_finished)
274+
275+    def apply_request_middleware(self, request):
276+        """
277+        Applies request middleware one at a time until a response is returned.
278+        If none of request middleware returns a response, then return None.
279+        """
280         for middleware_method in self._request_middleware:
281             response = middleware_method(request)
282             if response:
283                 return response
284 
285-        # Get urlconf from request object, if available.  Otherwise use default.
286-        urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
287-
288-        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
289+    def run_view_processing(self, request):
290+        """
291+        Apply view middleware and call view.
292+        """
293+        callback, callback_args, callback_kwargs = resolver(request).resolve(request.path)
294+
295+        # Apply view middleware
296+        for middleware_method in self._view_middleware:
297+            response = middleware_method(request, callback, callback_args, callback_kwargs)
298+            if response:
299+                return response
300+
301         try:
302-            callback, callback_args, callback_kwargs = resolver.resolve(request.path)
303-
304-            # Apply view middleware
305-            for middleware_method in self._view_middleware:
306-                response = middleware_method(request, callback, callback_args, callback_kwargs)
307+            response = callback(request, *callback_args, **callback_kwargs)
308+        except Exception, e:
309+            # If the view raised an exception, run it through exception
310+            # middleware, and if the exception middleware returns a
311+            # response, use that. Otherwise, reraise the exception.
312+            for middleware_method in self._exception_middleware:
313+                response = middleware_method(request, e)
314                 if response:
315                     return response
316+            raise
317 
318+        # Complain if the view returned None (a common error).
319+        if response is None:
320             try:
321-                response = callback(request, *callback_args, **callback_kwargs)
322-            except Exception, e:
323-                # If the view raised an exception, run it through exception
324-                # middleware, and if the exception middleware returns a
325-                # response, use that. Otherwise, reraise the exception.
326-                for middleware_method in self._exception_middleware:
327-                    response = middleware_method(request, e)
328-                    if response:
329-                        return response
330-                raise
331-
332-            # Complain if the view returned None (a common error).
333+                view_name = callback.func_name # If it's a function
334+            except AttributeError:
335+                view_name = callback.__class__.__name__ + '.__call__' # If it's a class
336+            raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
337+
338+        return response
339+
340+    def apply_response_middleware(self, request, response):
341+        """
342+        Applies all response middleware to the response.
343+        """
344+        for middleware_method in self._response_middleware:
345+            response = middleware_method(request, response)
346+        return response
347+
348+    def get_response(self, method, request, response=None):
349+        """
350+        Returns an HttpResponse by calling the passed method with the given
351+        HttpRequest object.  If an HttpResponse is passed, it will also be
352+        passed in the call to method.  Exceptions raised by calling method
353+        will be handled like so:
354+
355+        * Http404 exception - If settings.DEBUG is True, the
356+          technical_404_response debug view will be called.  If settings.DEBUG
357+          is False, the URL resolver's handler404 view will be called.
358+        * PermissionDenied exception - return HttpResponseForbidden.
359+        * SystemExit exception - ignored.
360+        * All other exceptions - If settings.DEBUG is True, the
361+          technical_500_response debug view will be called.  If settings.DEBUG
362+          is False, the URL resolver's handler500 view will be called.
363+        """
364+        from django.core import exceptions
365+        from django.core.mail import mail_admins
366+        from django.conf import settings
367+
368+        try:
369             if response is None:
370-                try:
371-                    view_name = callback.func_name # If it's a function
372-                except AttributeError:
373-                    view_name = callback.__class__.__name__ + '.__call__' # If it's a class
374-                raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
375-
376-            return response
377+                return method(request)
378+            else:
379+                return method(request, response)
380         except http.Http404, e:
381             if settings.DEBUG:
382                 from django.views import debug
383                 return debug.technical_404_response(request, e)
384             else:
385-                callback, param_dict = resolver.resolve404()
386+                callback, param_dict = resolver(request).resolve404()
387                 return callback(request, **param_dict)
388         except exceptions.PermissionDenied:
389             return http.HttpResponseForbidden('<h1>Permission denied</h1>')
390@@ -127,7 +224,7 @@
391                 message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
392                 mail_admins(subject, message, fail_silently=True)
393                 # Return an HttpResponse that displays a friendly error message.
394-                callback, param_dict = resolver.resolve500()
395+                callback, param_dict = resolver(request).resolve500()
396                 return callback(request, **param_dict)
397 
398     def _get_traceback(self, exc_info=None):
399@@ -144,4 +241,3 @@
400         for func in self.response_fixes:
401             response = func(request, response)
402         return response
403-
404
405=== modified file 'django/core/handlers/modpython.py'
406--- django/core/handlers/modpython.py   2007-12-02 02:22:19 +0000
407+++ django/core/handlers/modpython.py   2007-12-02 07:03:32 +0000
408@@ -2,9 +2,7 @@
409 from pprint import pformat
410 
411 from django import http
412-from django.core import signals
413 from django.core.handlers.base import BaseHandler
414-from django.dispatch import dispatcher
415 from django.utils import datastructures
416 from django.utils.encoding import force_unicode
417 
418@@ -151,21 +149,7 @@
419         if self._request_middleware is None:
420             self.load_middleware()
421 
422-        dispatcher.send(signal=signals.request_started)
423-        try:
424-            try:
425-                request = self.request_class(req)
426-            except UnicodeDecodeError:
427-                response = http.HttpResponseBadRequest()
428-            else:
429-                response = self.get_response(request)
430-
431-                # Apply response middleware
432-                for middleware_method in self._response_middleware:
433-                    response = middleware_method(request, response)
434-                response = self.apply_response_fixes(request, response)
435-        finally:
436-            dispatcher.send(signal=signals.request_finished)
437+        response = self.handle_request(req)
438 
439         # Convert our custom HttpResponse object back into the mod_python req.
440         req.content_type = response['Content-Type']
441
442=== modified file 'django/core/handlers/wsgi.py'
443--- django/core/handlers/wsgi.py        2007-12-02 02:22:19 +0000
444+++ django/core/handlers/wsgi.py        2007-12-02 07:23:13 +0000
445@@ -6,9 +6,7 @@
446     from StringIO import StringIO
447 
448 from django import http
449-from django.core import signals
450 from django.core.handlers.base import BaseHandler
451-from django.dispatch import dispatcher
452 from django.utils import datastructures
453 from django.utils.encoding import force_unicode
454 
455@@ -195,21 +193,7 @@
456                 self.load_middleware()
457             self.initLock.release()
458 
459-        dispatcher.send(signal=signals.request_started)
460-        try:
461-            try:
462-                request = self.request_class(environ)
463-            except UnicodeDecodeError:
464-                response = http.HttpResponseBadRequest()
465-            else:
466-                response = self.get_response(request)
467-
468-                # Apply response middleware
469-                for middleware_method in self._response_middleware:
470-                    response = middleware_method(request, response)
471-                response = self.apply_response_fixes(request, response)
472-        finally:
473-            dispatcher.send(signal=signals.request_finished)
474+        response = self.handle_request(environ)
475 
476         try:
477             status_text = STATUS_CODE_TEXT[response.status_code]
478
479=== modified file 'django/test/client.py'
480--- django/test/client.py       2007-11-11 03:55:44 +0000
481+++ django/test/client.py       2007-12-02 20:42:25 +0000
482@@ -25,28 +25,17 @@
483     Uses the WSGI interface to compose requests, but returns
484     the raw HttpResponse object
485     """
486+    request_class = WSGIRequest
487+
488     def __call__(self, environ):
489         from django.conf import settings
490-        from django.core import signals
491 
492         # Set up middleware if needed. We couldn't do this earlier, because
493         # settings weren't available.
494         if self._request_middleware is None:
495             self.load_middleware()
496 
497-        dispatcher.send(signal=signals.request_started)
498-        try:
499-            request = WSGIRequest(environ)
500-            response = self.get_response(request)
501-
502-            # Apply response middleware
503-            for middleware_method in self._response_middleware:
504-                response = middleware_method(request, response)
505-            response = self.apply_response_fixes(request, response)
506-        finally:
507-            dispatcher.send(signal=signals.request_finished)
508-
509-        return response
510+        return self.handle_request(environ)
511 
512 def store_rendered_templates(store, signal, sender, template, context):
513     "A utility function for storing templates and contexts that are rendered"
514@@ -96,7 +85,7 @@
515     ])
516     return '\r\n'.join(lines)
517 
518-class Client:
519+class Client(object):
520     """
521     A class that can act as a client for testing purposes.
522 
523@@ -119,6 +108,7 @@
524         self.defaults = defaults
525         self.cookies = SimpleCookie()
526         self.exc_info = None
527+        self.reraise_exceptions = True
528 
529     def store_exc_info(self, *args, **kwargs):
530         """
531@@ -180,7 +170,7 @@
532                 raise
533 
534         # Look for a signalled exception and reraise it
535-        if self.exc_info:
536+        if self.exc_info and self.reraise_exceptions:
537             raise self.exc_info[1], None, self.exc_info[2]
538 
539         # Save the client and request that stimulated the response
540@@ -262,7 +252,7 @@
541             self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
542 
543             # Save the session values
544-            request.session.save()   
545+            request.session.save()
546 
547             return True
548         else:
549
550=== modified file 'tests/urls.py'
551--- tests/urls.py       2007-12-02 23:25:55 +0000
552+++ tests/urls.py       2007-12-03 04:31:57 +0000
553@@ -11,10 +11,13 @@
554 
555     # test urlconf for {% url %} template tag
556     (r'^url_tag/', include('regressiontests.templates.urls')),
557-   
558+
559     # django built-in views
560     (r'^views/', include('regressiontests.views.urls')),
561 
562     # test urlconf for middleware tests
563     (r'^middleware/', include('regressiontests.middleware.urls')),
564+   
565+    # urlconf for handler_exceptions tests.
566+    (r'^handler_exceptions/', include('regressiontests.handler_exceptions.urls')),
567 )
568