From 21bf685f5e660a187fcac31e84d5cd89758aeb74 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sat, 7 Nov 2015 17:24:07 +0100 Subject: [PATCH] Fixed #25697 -- Made default error views error when passed a nonexistent template_name. --- django/views/defaults.py | 25 +++++++++++++++++++---- docs/ref/views.txt | 16 +++++++++++++++ docs/releases/1.10.txt | 3 +++ tests/view_tests/tests/test_defaults.py | 27 ++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 5 deletions(-) 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')