Skip to content

Commit 20b1b6d

Browse files
committed
Use simpler error pages when HTML isn't needed
This saves queries and processing time, if the HTML isn't ever going to be rendered, and simple text would be enough.
1 parent 41b7d85 commit 20b1b6d

File tree

3 files changed

+82
-12
lines changed

3 files changed

+82
-12
lines changed

tbx/core/tests/test_views.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,29 @@ def test_accessible(self) -> None:
1313
"http://testserver/.well-known/security.txt",
1414
)
1515
self.assertIn("no-cache", response["Cache-Control"])
16+
17+
18+
class PageNotFoundTestCase(TestCase):
19+
url = "/does-not-exist/"
20+
21+
def test_accessible(self) -> None:
22+
response = self.client.get(self.url)
23+
self.assertEqual(response.status_code, 404)
24+
self.assertIn("text/html", response.headers["content-type"])
25+
26+
def test_accept_html(self) -> None:
27+
response = self.client.get(self.url, headers={"Accept": "text/html"})
28+
self.assertEqual(response.status_code, 404)
29+
self.assertIn("text/html", response.headers["content-type"])
30+
31+
def test_simple_when_doesnt_accept_html(self) -> None:
32+
response = self.client.get(self.url, headers={"Accept": "text/css"})
33+
self.assertEqual(response.status_code, 404)
34+
self.assertIn("text/plain", response.headers["content-type"])
35+
36+
def test_simple_when_html_not_highest(self) -> None:
37+
response = self.client.get(
38+
self.url, headers={"Accept": "text/html;q=0.8,text/css"}
39+
)
40+
self.assertEqual(response.status_code, 404)
41+
self.assertIn("text/plain", response.headers["content-type"])

tbx/core/utils/views.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
1+
from django.http import HttpResponseNotFound, HttpResponseServerError
12
from django.views import defaults
3+
from django.views.decorators.csrf import requires_csrf_token
4+
from django.views.decorators.vary import vary_on_headers
25

36

4-
def page_not_found(request, exception, template_name="patterns/pages/errors/404.html"):
5-
return defaults.page_not_found(request, exception, template_name)
7+
def get_quality(media_type):
8+
return float(media_type.params.get("q", 1))
69

710

11+
def show_html_error_page(request):
12+
accepted_types = sorted(request.accepted_types, key=get_quality, reverse=True)
13+
14+
html_type = next(
15+
(
16+
accepted_type
17+
for accepted_type in accepted_types
18+
if accepted_type.match("text/html")
19+
),
20+
None,
21+
)
22+
23+
# If HTML isn't accepted, don't serve it
24+
if html_type is None:
25+
return False
26+
27+
max_quality = get_quality(accepted_types[0])
28+
29+
# If HTML isn't the highest quality, don't serve it
30+
if get_quality(html_type) < max_quality:
31+
return False
32+
33+
return True
34+
35+
36+
@vary_on_headers("Accept")
37+
@requires_csrf_token
38+
def page_not_found(
39+
request, exception=None, template_name="patterns/pages/errors/404.html"
40+
):
41+
if show_html_error_page(request):
42+
return defaults.page_not_found(request, exception, template_name)
43+
44+
# Serve a simpler, cheaper 404 page if we don't need to
45+
return HttpResponseNotFound(
46+
"Page not found", content_type="text/plain; charset=utf-8"
47+
)
48+
49+
50+
@vary_on_headers("Accept")
51+
@requires_csrf_token
852
def server_error(request, template_name="patterns/pages/errors/500.html"):
9-
return defaults.server_error(request, template_name)
53+
if show_html_error_page(request):
54+
return defaults.server_error(request, template_name)
55+
56+
# Serve a simpler, cheaper 500 page if we don't need to
57+
return HttpResponseServerError(
58+
"Server error", content_type="text/plain; charset=utf-8"
59+
)

tbx/urls.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
get_default_cache_control_decorator,
1111
get_default_cache_control_method_decorator,
1212
)
13+
from tbx.core.utils.views import page_not_found, server_error
1314
from tbx.core.views import robots
1415
from wagtail import urls as wagtail_urls
1516
from wagtail.admin import urls as wagtailadmin_urls
@@ -32,7 +33,6 @@
3233
if settings.DEBUG:
3334
from django.conf.urls.static import static
3435
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
35-
from django.views.generic import TemplateView
3636

3737
# Serve static and media files from development server
3838
urlpatterns += staticfiles_urlpatterns()
@@ -41,14 +41,8 @@
4141
# Add views for testing 404 and 500 templates
4242
urlpatterns += [
4343
# Add views for testing 404 and 500 templates
44-
path(
45-
"test404/",
46-
TemplateView.as_view(template_name="patterns/pages/errors/404.html"),
47-
),
48-
path(
49-
"test500/",
50-
TemplateView.as_view(template_name="patterns/pages/errors/500.html"),
51-
),
44+
path("test404/", page_not_found),
45+
path("test500/", server_error),
5246
]
5347

5448
# Django Debug Toolbar

0 commit comments

Comments
 (0)