diff --git a/django/views/defaults.py b/django/views/defaults.py
index d4651e6665..838e5c1f85 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -4,12 +4,17 @@ from django.utils import six
from django.utils.encoding import force_text
from django.views.decorators.csrf import requires_csrf_token
+ERROR_404_TEMPLATE_NAME = '404.html'
+ERROR_403_TEMPLATE_NAME = '403.html'
+ERROR_400_TEMPLATE_NAME = '400.html'
+ERROR_500_TEMPLATE_NAME = '500.html'
+
# 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, exception, template_name='404.html'):
+def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
"""
Default 404 handler.
@@ -40,6 +45,9 @@ def page_not_found(request, exception, template_name='404.html'):
body = template.render(context, request)
content_type = None # Django will use DEFAULT_CONTENT_TYPE
except TemplateDoesNotExist:
+ if template_name != ERROR_404_TEMPLATE_NAME:
+ # Reraise if it's a missing custom template.
+ raise
template = Engine().from_string(
'
Not Found
'
'The requested URL {{ request_path }} was not found on this server.
')
@@ -49,7 +57,7 @@ def page_not_found(request, exception, template_name='404.html'):
@requires_csrf_token
-def server_error(request, template_name='500.html'):
+def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
"""
500 error handler.
@@ -59,12 +67,15 @@ def server_error(request, template_name='500.html'):
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
+ if template_name != ERROR_500_TEMPLATE_NAME:
+ # Reraise if it's a missing custom template.
+ raise
return http.HttpResponseServerError('Server Error (500)
', content_type='text/html')
return http.HttpResponseServerError(template.render())
@requires_csrf_token
-def bad_request(request, exception, template_name='400.html'):
+def bad_request(request, exception, template_name=ERROR_400_TEMPLATE_NAME):
"""
400 error handler.
@@ -74,6 +85,9 @@ def bad_request(request, exception, template_name='400.html'):
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
+ if template_name != ERROR_400_TEMPLATE_NAME:
+ # Reraise if it's a missing custom template.
+ raise
return http.HttpResponseBadRequest('Bad Request (400)
', content_type='text/html')
# No exception content is passed to the template, to not disclose any sensitive information.
return http.HttpResponseBadRequest(template.render())
@@ -83,7 +97,7 @@ def bad_request(request, exception, template_name='400.html'):
# therefore need @requires_csrf_token in case the template needs
# {% csrf_token %}.
@requires_csrf_token
-def permission_denied(request, exception, template_name='403.html'):
+def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME):
"""
Permission denied (403) handler.
@@ -96,6 +110,9 @@ def permission_denied(request, exception, template_name='403.html'):
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
+ if template_name != ERROR_403_TEMPLATE_NAME:
+ # Reraise if it's a missing custom template.
+ raise
return http.HttpResponseForbidden('403 Forbidden
', content_type='text/html')
return http.HttpResponseForbidden(
template.render(request=request, context={'exception': force_text(exception)})
diff --git a/docs/ref/views.txt b/docs/ref/views.txt
index 133a79814b..8069264039 100644
--- a/docs/ref/views.txt
+++ b/docs/ref/views.txt
@@ -93,6 +93,10 @@ Three things to note about 404 views:
second parameter, the exception that triggered the error. A useful
representation of the exception is also passed in the template context.
+.. versionchanged:: 1.10
+
+ Passing a nonexistent ``template_name`` will raise ``TemplateDoesNotExist``.
+
.. _http_internal_server_error_view:
The 500 (server error) view
@@ -113,6 +117,10 @@ 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.
+.. versionchanged:: 1.10
+
+ Passing a nonexistent ``template_name`` will raise ``TemplateDoesNotExist``.
+
.. _http_forbidden_view:
The 403 (HTTP Forbidden) view
@@ -148,6 +156,10 @@ view you can use code like this::
unicode representation of the exception is also passed in the template
context.
+.. versionchanged:: 1.10
+
+ Passing a nonexistent ``template_name`` will raise ``TemplateDoesNotExist``.
+
.. _http_bad_request_view:
The 400 (bad request) view
@@ -173,3 +185,7 @@ filesystem paths.
The signature of ``bad_request()`` changed in Django 1.9. The function
now accepts a second parameter, the exception that triggered the error.
+
+.. versionchanged:: 1.10
+
+ Passing a nonexistent ``template_name`` will raise ``TemplateDoesNotExist``.
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index 33ca4bd12c..ff6900243f 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -464,6 +464,9 @@ Miscellaneous
now better to call the ``LogEntry.get_change_message()`` method which will
provide the message in the current language.
+* The default error views now raise ``TemplateDoesNotExist`` if a nonexistent
+ ``template_name`` is specified.
+
.. _deprecated-features-1.10:
Features deprecated in 1.10
diff --git a/tests/view_tests/tests/test_defaults.py b/tests/view_tests/tests/test_defaults.py
index a729276887..1ef8bbaf74 100644
--- a/tests/view_tests/tests/test_defaults.py
+++ b/tests/view_tests/tests/test_defaults.py
@@ -4,8 +4,13 @@ import datetime
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
-from django.test import TestCase
+from django.http import Http404
+from django.template import TemplateDoesNotExist
+from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
+from django.views.defaults import (
+ bad_request, page_not_found, permission_denied, server_error,
+)
from ..models import Article, Author, UrlArticle
@@ -123,3 +128,23 @@ class DefaultsTests(TestCase):
response = self.client.get('/server_error/')
self.assertEqual(response['Content-Type'], 'text/html')
+
+ def test_custom_templates_wrong(self):
+ """
+ Default error views should raise TemplateDoesNotExist when passed a
+ template that doesn't exist.
+ """
+ rf = RequestFactory()
+ request = rf.get('/')
+
+ with self.assertRaises(TemplateDoesNotExist):
+ bad_request(request, Exception(), template_name='nonexistent')
+
+ with self.assertRaises(TemplateDoesNotExist):
+ permission_denied(request, Exception(), template_name='nonexistent')
+
+ with self.assertRaises(TemplateDoesNotExist):
+ page_not_found(request, Http404(), template_name='nonexistent')
+
+ with self.assertRaises(TemplateDoesNotExist):
+ server_error(request, template_name='nonexistent')