Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #9847 -- Added 403 response handler. Many thanks to kgrandis, a…

…damnelson, vkryachko, fvox13 and Chris Beaven.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16606 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1ca6e9b9e24240033349c93b05902c79c0a25bbb 1 parent 958e049
@jezdez jezdez authored
View
1  AUTHORS
@@ -457,6 +457,7 @@ answer newbie questions, and generally made Django that much better:
Ben Slavin <benjamin.slavin@gmail.com>
sloonz <simon.lipp@insa-lyon.fr>
Paul Smith <blinkylights23@gmail.com>
+ Steven L. Smith (fvox13) <steven@stevenlsmith.com>
Warren Smith <warren@wandrsmith.net>
smurf@smurf.noris.de
Vsevolod Solovyov
View
1  django/conf/urls/defaults.py
@@ -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'
View
22 django/core/handlers/base.py
@@ -154,12 +154,22 @@ def get_response(self, request):
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
View
3  django/core/urlresolvers.py
@@ -331,6 +331,9 @@ def _resolve_special(self, view_type):
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')
View
29 django/views/defaults.py
@@ -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 @@ def server_error(request, template_name='500.html'):
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.
View
5 docs/releases/1.4.txt
@@ -261,6 +261,11 @@ Django 1.4 also includes several smaller improvements worth noting:
* 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
View
21 docs/topics/http/views.txt
@@ -197,3 +197,24 @@ Two things to note about 500 views:
* 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'
View
22 tests/regressiontests/views/tests/debug.py
@@ -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 @@ def test_files(self):
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)
View
5 tests/regressiontests/views/urls.py
@@ -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')),
View
6 tests/regressiontests/views/views.py
@@ -1,8 +1,9 @@
import sys
from django import forms
-from django.http import HttpResponse, HttpResponseRedirect
+from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import get_resolver
+from django.http import HttpResponse, HttpResponseRedirect
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 @@ def raises404(request):
resolver = get_resolver(None)
resolver.resolve('')
+def raises403(request):
+ raise PermissionDenied
+
def redirect(request):
"""
Forces an HTTP redirect.
Please sign in to comment.
Something went wrong with that request. Please try again.