Author: jezdez
Date: 2011-08-12 07:15:31 -0700 (Fri, 12 Aug 2011)
New Revision: 16606

Modified:
   django/trunk/AUTHORS
   django/trunk/django/conf/urls/defaults.py
   django/trunk/django/core/handlers/base.py
   django/trunk/django/core/urlresolvers.py
   django/trunk/django/views/defaults.py
   django/trunk/docs/releases/1.4.txt
   django/trunk/docs/topics/http/views.txt
   django/trunk/tests/regressiontests/views/tests/debug.py
   django/trunk/tests/regressiontests/views/urls.py
   django/trunk/tests/regressiontests/views/views.py
Log:
Fixed #9847 -- Added 403 response handler. Many thanks to kgrandis, adamnelson, 
vkryachko, fvox13  and Chris Beaven.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2011-08-12 14:15:17 UTC (rev 16605)
+++ django/trunk/AUTHORS        2011-08-12 14:15:31 UTC (rev 16606)
@@ -457,6 +457,7 @@
     Ben Slavin <[email protected]>
     sloonz <[email protected]>
     Paul Smith <[email protected]>
+    Steven L. Smith (fvox13) <[email protected]>
     Warren Smith <[email protected]>
     [email protected]
     Vsevolod Solovyov

Modified: django/trunk/django/conf/urls/defaults.py
===================================================================
--- django/trunk/django/conf/urls/defaults.py   2011-08-12 14:15:17 UTC (rev 
16605)
+++ django/trunk/django/conf/urls/defaults.py   2011-08-12 14:15:31 UTC (rev 
16606)
@@ -6,6 +6,7 @@
 
 __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
 
+handler403 = 'django.views.defaults.permission_denied'
 handler404 = 'django.views.defaults.page_not_found'
 handler500 = 'django.views.defaults.server_error'
 

Modified: django/trunk/django/core/handlers/base.py
===================================================================
--- django/trunk/django/core/handlers/base.py   2011-08-12 14:15:17 UTC (rev 
16605)
+++ django/trunk/django/core/handlers/base.py   2011-08-12 14:15:31 UTC (rev 
16606)
@@ -154,12 +154,22 @@
                         finally:
                             receivers = 
signals.got_request_exception.send(sender=self.__class__, request=request)
             except exceptions.PermissionDenied:
-                logger.warning('Forbidden (Permission denied): %s' % 
request.path,
-                            extra={
-                                'status_code': 403,
-                                'request': request
-                            })
-                response = http.HttpResponseForbidden('<h1>Permission 
denied</h1>')
+                logger.warning(
+                    'Forbidden (Permission denied): %s' % request.path,
+                    extra={
+                        'status_code': 403,
+                        'request': request
+                    })
+                try:
+                    callback, param_dict = resolver.resolve403()
+                    response = callback(request, **param_dict)
+                except:
+                    try:
+                        response = self.handle_uncaught_exception(request,
+                            resolver, sys.exc_info())
+                    finally:
+                        receivers = signals.got_request_exception.send(
+                            sender=self.__class__, request=request)
             except SystemExit:
                 # Allow sys.exit() to actually exit. See tickets #1023 and 
#4701
                 raise

Modified: django/trunk/django/core/urlresolvers.py
===================================================================
--- django/trunk/django/core/urlresolvers.py    2011-08-12 14:15:17 UTC (rev 
16605)
+++ django/trunk/django/core/urlresolvers.py    2011-08-12 14:15:31 UTC (rev 
16606)
@@ -331,6 +331,9 @@
             callback = getattr(defaults, 'handler%s' % view_type)
         return get_callable(callback), {}
 
+    def resolve403(self):
+        return self._resolve_special('403')
+
     def resolve404(self):
         return self._resolve_special('404')
 

Modified: django/trunk/django/views/defaults.py
===================================================================
--- django/trunk/django/views/defaults.py       2011-08-12 14:15:17 UTC (rev 
16605)
+++ django/trunk/django/views/defaults.py       2011-08-12 14:15:31 UTC (rev 
16606)
@@ -1,10 +1,12 @@
 from django import http
+from django.template import (Context, RequestContext,
+                             loader, TemplateDoesNotExist)
 from django.views.decorators.csrf import requires_csrf_token
-from django.template import Context, RequestContext, loader
 
 
-# This can be called when CsrfViewMiddleware.process_view has not run, 
therefore
-# need @requires_csrf_token in case the template needs {% csrf_token %}.
+# This can be called when CsrfViewMiddleware.process_view has not run,
+# therefore need @requires_csrf_token in case the template needs
+# {% csrf_token %}.
 @requires_csrf_token
 def page_not_found(request, template_name='404.html'):
     """
@@ -31,6 +33,27 @@
     return http.HttpResponseServerError(t.render(Context({})))
 
 
+# This can be called when CsrfViewMiddleware.process_view has not run,
+# therefore need @requires_csrf_token in case the template needs
+# {% csrf_token %}.
+@requires_csrf_token
+def permission_denied(request, template_name='403.html'):
+    """
+    Permission denied (403) handler.
+
+    Templates: `403.html`
+    Context: None
+
+    If the template does not exist, an Http403 response containing the text
+    "403 Forbidden" (as per RFC 2616) will be returned.
+    """
+    try:
+        template = loader.get_template(template_name)
+    except TemplateDoesNotExist:
+        return http.HttpResponseForbidden('<h1>403 Forbidden</h1>')
+    return http.HttpResponseForbidden(template.render(RequestContext(request)))
+
+
 def shortcut(request, content_type_id, object_id):
     # TODO: Remove this in Django 2.0.
     # This is a legacy view that depends on the contenttypes framework.

Modified: django/trunk/docs/releases/1.4.txt
===================================================================
--- django/trunk/docs/releases/1.4.txt  2011-08-12 14:15:17 UTC (rev 16605)
+++ django/trunk/docs/releases/1.4.txt  2011-08-12 14:15:31 UTC (rev 16606)
@@ -261,6 +261,11 @@
 * It is now possible to load fixtures containing forward references when using
   MySQL with the InnoDB database engine.
 
+* A new 403 response handler has been added as
+  ``'django.views.defaults.permission_denied'``. See the documentation
+  about :ref:`the 403 (HTTP Forbidden) view<http_forbidden_view>` for more
+  information.
+
 .. _backwards-incompatible-changes-1.4:
 
 Backwards incompatible changes in 1.4

Modified: django/trunk/docs/topics/http/views.txt
===================================================================
--- django/trunk/docs/topics/http/views.txt     2011-08-12 14:15:17 UTC (rev 
16605)
+++ django/trunk/docs/topics/http/views.txt     2011-08-12 14:15:31 UTC (rev 
16606)
@@ -197,3 +197,24 @@
     * If :setting:`DEBUG` is set to ``True`` (in your settings module), then
       your 500 view will never be used, and the traceback will be displayed
       instead, with some debug information.
+
+.. _http_forbidden_view:
+
+The 403 (HTTP Forbidden) view
+----------------------------
+
+.. versionadded:: 1.4
+
+In the same vein as the 404 and 500 views, Django has a view to handle 403
+Forbidden errors. If a view results in a 403 exception then Django will, by
+default, call the view ``django.views.defaults.permission_denied``.
+
+This view loads and renders the template ``403.html`` in your root template
+directory, or if this file does not exist, instead serves the text
+"403 Forbidden", as per RFC 2616 (the HTTP 1.1 Specification).
+
+It is possible to override ``django.views.defaults.permission_denied`` in the
+same way you can for the 404 and 500 views by specifying a ``handler403`` in
+your URLconf::
+
+    handler403 = 'mysite.views.my_custom_permission_denied_view'

Modified: django/trunk/tests/regressiontests/views/tests/debug.py
===================================================================
--- django/trunk/tests/regressiontests/views/tests/debug.py     2011-08-12 
14:15:17 UTC (rev 16605)
+++ django/trunk/tests/regressiontests/views/tests/debug.py     2011-08-12 
14:15:31 UTC (rev 16606)
@@ -6,6 +6,8 @@
 from django.conf import settings
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import TestCase, RequestFactory
+from django.test.utils import (setup_test_template_loader,
+                               restore_template_loaders)
 from django.core.urlresolvers import reverse
 from django.template import TemplateSyntaxError
 from django.views.debug import ExceptionReporter
@@ -40,6 +42,26 @@
         self.assertTrue('file_data.txt' in response.content)
         self.assertFalse('haha' in response.content)
 
+    def test_403(self):
+        # Ensure no 403.html template exists to test the default case.
+        setup_test_template_loader({})
+        try:
+            response = self.client.get('/views/raises403/')
+            self.assertContains(response, '<h1>403 Forbidden</h1>', 
status_code=403)
+        finally:
+            restore_template_loaders()
+
+    def test_403_template(self):
+        # Set up a test 403.html template.
+        setup_test_template_loader(
+            {'403.html': 'This is a test template for a 403 Forbidden error.'}
+        )
+        try:
+            response = self.client.get('/views/raises403/')
+            self.assertContains(response, 'test template', status_code=403)
+        finally:
+            restore_template_loaders()
+
     def test_404(self):
         response = self.client.get('/views/raises404/')
         self.assertEqual(response.status_code, 404)

Modified: django/trunk/tests/regressiontests/views/urls.py
===================================================================
--- django/trunk/tests/regressiontests/views/urls.py    2011-08-12 14:15:17 UTC 
(rev 16605)
+++ django/trunk/tests/regressiontests/views/urls.py    2011-08-12 14:15:31 UTC 
(rev 16606)
@@ -39,8 +39,9 @@
     (r'^server_error/', 'django.views.defaults.server_error'),
 
     # a view that raises an exception for the debug view
-    (r'^raises/$', views.raises),
-    (r'^raises404/$', views.raises404),
+    (r'raises/$', views.raises),
+    (r'raises404/$', views.raises404),
+    (r'raises403/$', views.raises403),
 
     # i18n views
     (r'^i18n/', include('django.conf.urls.i18n')),

Modified: django/trunk/tests/regressiontests/views/views.py
===================================================================
--- django/trunk/tests/regressiontests/views/views.py   2011-08-12 14:15:17 UTC 
(rev 16605)
+++ django/trunk/tests/regressiontests/views/views.py   2011-08-12 14:15:31 UTC 
(rev 16606)
@@ -1,8 +1,9 @@
 import sys
 
 from django import forms
+from django.core.exceptions import PermissionDenied
+from django.core.urlresolvers import get_resolver
 from django.http import HttpResponse, HttpResponseRedirect
-from django.core.urlresolvers import get_resolver
 from django.shortcuts import render_to_response, render
 from django.template import Context, RequestContext, TemplateDoesNotExist
 from django.views.debug import technical_500_response, 
SafeExceptionReporterFilter
@@ -53,6 +54,9 @@
     resolver = get_resolver(None)
     resolver.resolve('')
 
+def raises403(request):
+    raise PermissionDenied
+
 def redirect(request):
     """
     Forces an HTTP redirect.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to