mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #35143 -- Improved accessibility of 404/500 debug pages.
This: - changes the header, main, and footer content areas to be rendered in a <header>, <main>, and <footer> tags, - adds scope attributes to <th>, - uses <code> for a patterns list, - uses <small> instead of <span>.
This commit is contained in:
parent
c317e81378
commit
b9e2a3fc63
1
AUTHORS
1
AUTHORS
@ -638,6 +638,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Marc Tamlyn <marc.tamlyn@gmail.com>
|
||||
Marc-Aurèle Brothier <ma.brothier@gmail.com>
|
||||
Marian Andre <django@andre.sk>
|
||||
Marijke Luttekes <mail@marijkeluttekes.dev>
|
||||
Marijn Vriens <marijn@metronomo.cl>
|
||||
Mario Gonzalez <gonzalemario@gmail.com>
|
||||
Mariusz Felisiak <felisiak.mariusz@gmail.com>
|
||||
|
@ -9,9 +9,9 @@
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background:#eee; color:#000; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
body > :where(header, main, footer) { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; margin-bottom:.4em; }
|
||||
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
||||
h1 small { font-size:60%; color:#666; font-weight:normal; }
|
||||
table { border:none; border-collapse: collapse; width:100%; }
|
||||
td, th { vertical-align:top; padding:2px 3px; }
|
||||
th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
||||
@ -24,27 +24,28 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<h1>Page not found <span>(404)</span></h1>
|
||||
<header id="summary">
|
||||
<h1>Page not found <small>(404)</small></h1>
|
||||
{% if reason and resolved %}<pre class="exception_value">{{ reason }}</pre>{% endif %}
|
||||
<table class="meta">
|
||||
<tr>
|
||||
<th>Request Method:</th>
|
||||
<th scope="row">Request Method:</th>
|
||||
<td>{{ request.META.REQUEST_METHOD }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Request URL:</th>
|
||||
<th scope="row">Request URL:</th>
|
||||
<td>{{ request.build_absolute_uri }}</td>
|
||||
</tr>
|
||||
{% if raising_view_name %}
|
||||
<tr>
|
||||
<th>Raised by:</th>
|
||||
<th scope="row">Raised by:</th>
|
||||
<td>{{ raising_view_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div id="info">
|
||||
</header>
|
||||
|
||||
<main id="info">
|
||||
{% if urlpatterns %}
|
||||
<p>
|
||||
Using the URLconf defined in <code>{{ urlconf }}</code>,
|
||||
@ -54,8 +55,10 @@
|
||||
{% for pattern in urlpatterns %}
|
||||
<li>
|
||||
{% for pat in pattern %}
|
||||
<code>
|
||||
{{ pat.pattern }}
|
||||
{% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %}
|
||||
</code>
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
@ -69,14 +72,14 @@
|
||||
{% if resolved %}matched the last one.{% else %}didn’t match any of these.{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="explanation">
|
||||
<footer id="explanation">
|
||||
<p>
|
||||
You’re seeing this error because you have <code>DEBUG = True</code> in
|
||||
your Django settings file. Change that to <code>False</code>, and Django
|
||||
will display a standard 404 page.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -10,7 +10,7 @@
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background-color:#fff; color:#000; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
body > :where(header, main, footer) { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; }
|
||||
h2 { margin-bottom:.8em; }
|
||||
h3 { margin:1em 0 .5em 0; }
|
||||
@ -47,6 +47,8 @@
|
||||
.user div.commands a { color: black; }
|
||||
#summary { background: #ffc; }
|
||||
#summary h2 { font-weight: normal; color: #666; }
|
||||
#info { padding: 0; }
|
||||
#info > * { padding:10px 20px; }
|
||||
#explanation { background:#eee; }
|
||||
#template, #template-not-exist { background:#f6f6f6; }
|
||||
#template-not-exist ul { margin: 0 0 10px 20px; }
|
||||
@ -97,67 +99,69 @@
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<header id="summary">
|
||||
<h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}
|
||||
{% if request %} at {{ request.path_info }}{% endif %}</h1>
|
||||
<pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% if exception_notes %}{{ exception_notes }}{% endif %}{% else %}No exception message supplied{% endif %}</pre>
|
||||
<table class="meta">
|
||||
{% if request %}
|
||||
<tr>
|
||||
<th>Request Method:</th>
|
||||
<th scope="row">Request Method:</th>
|
||||
<td>{{ request.META.REQUEST_METHOD }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Request URL:</th>
|
||||
<th scope="row">Request URL:</th>
|
||||
<td>{{ request_insecure_uri }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Django Version:</th>
|
||||
<th scope="row">Django Version:</th>
|
||||
<td>{{ django_version_info }}</td>
|
||||
</tr>
|
||||
{% if exception_type %}
|
||||
<tr>
|
||||
<th>Exception Type:</th>
|
||||
<th scope="row">Exception Type:</th>
|
||||
<td>{{ exception_type }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if exception_type and exception_value %}
|
||||
<tr>
|
||||
<th>Exception Value:</th>
|
||||
<th scope="row">Exception Value:</th>
|
||||
<td><pre>{{ exception_value|force_escape }}</pre></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if lastframe %}
|
||||
<tr>
|
||||
<th>Exception Location:</th>
|
||||
<th scope="row">Exception Location:</th>
|
||||
<td><span class="fname">{{ lastframe.filename }}</span>, line {{ lastframe.lineno }}, in {{ lastframe.function }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if raising_view_name %}
|
||||
<tr>
|
||||
<th>Raised during:</th>
|
||||
<th scope="row">Raised during:</th>
|
||||
<td>{{ raising_view_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Python Executable:</th>
|
||||
<th scope="row">Python Executable:</th>
|
||||
<td>{{ sys_executable }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Python Version:</th>
|
||||
<th scope="row">Python Version:</th>
|
||||
<td>{{ sys_version_info }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Python Path:</th>
|
||||
<td><pre>{{ sys_path|pprint }}</pre></td>
|
||||
<th scope="row">Python Path:</th>
|
||||
<td><pre><code>{{ sys_path|pprint }}</code></pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Server time:</th>
|
||||
<th scope="row">Server time:</th>
|
||||
<td>{{server_time|date:"r"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="info">
|
||||
{% if unicode_hint %}
|
||||
<div id="unicode-hint">
|
||||
<h2>Unicode error hint</h2>
|
||||
@ -195,11 +199,11 @@
|
||||
{% if template_info.bottom != template_info.total %} cut-bottom{% endif %}">
|
||||
{% for source_line in template_info.source_lines %}
|
||||
{% if source_line.0 == template_info.line %}
|
||||
<tr class="error"><th>{{ source_line.0 }}</th>
|
||||
<tr class="error"><th scope="row">{{ source_line.0 }}</th>
|
||||
<td>{{ template_info.before }}<span class="specific">{{ template_info.during }}</span>{{ template_info.after }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><th>{{ source_line.0 }}</th>
|
||||
<tr><th scope="row">{{ source_line.0 }}</th>
|
||||
<td>{{ source_line.1 }}</td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@ -266,8 +270,8 @@
|
||||
<table class="vars" id="v{{ frame.id }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -354,8 +358,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -376,8 +380,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -398,8 +402,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -420,8 +424,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -441,8 +445,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -463,8 +467,8 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Value</th>
|
||||
<th scope="col">Setting</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -478,14 +482,16 @@ Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ ex
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% if not is_email %}
|
||||
<div id="explanation">
|
||||
<footer id="explanation">
|
||||
<p>
|
||||
You’re seeing this error because you have <code>DEBUG = True</code> in your
|
||||
Django settings file. Change that to <code>False</code>, and Django will
|
||||
display a standard page generated by the handler for this status code.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -156,7 +156,8 @@ Email
|
||||
Error Reporting
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
* ...
|
||||
* In order to improve accessibility, the technical 404 and 500 error pages now
|
||||
use HTML landmark elements for the header, footer, and main content areas.
|
||||
|
||||
File Storage
|
||||
~~~~~~~~~~~~
|
||||
|
@ -176,6 +176,12 @@ class DebugViewTests(SimpleTestCase):
|
||||
self.assertContains(
|
||||
response, "Django tried these URL patterns", status_code=404
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"<code>technical404/ [name='my404']</code>",
|
||||
status_code=404,
|
||||
html=True,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"<p>The current path, <code>not-in-urls</code>, didn’t match any "
|
||||
@ -204,6 +210,9 @@ class DebugViewTests(SimpleTestCase):
|
||||
|
||||
def test_technical_404(self):
|
||||
response = self.client.get("/technical404/")
|
||||
self.assertContains(response, '<header id="summary">', status_code=404)
|
||||
self.assertContains(response, '<main id="info">', status_code=404)
|
||||
self.assertContains(response, '<footer id="explanation">', status_code=404)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<pre class="exception_value">Testing technical 404.</pre>',
|
||||
@ -228,7 +237,7 @@ class DebugViewTests(SimpleTestCase):
|
||||
response = self.client.get("/classbased404/")
|
||||
self.assertContains(
|
||||
response,
|
||||
"<th>Raised by:</th><td>view_tests.views.Http404View</td>",
|
||||
'<th scope="row">Raised by:</th><td>view_tests.views.Http404View</td>',
|
||||
status_code=404,
|
||||
html=True,
|
||||
)
|
||||
@ -236,9 +245,12 @@ class DebugViewTests(SimpleTestCase):
|
||||
def test_technical_500(self):
|
||||
with self.assertLogs("django.request", "ERROR"):
|
||||
response = self.client.get("/raises500/")
|
||||
self.assertContains(response, '<header id="summary">', status_code=500)
|
||||
self.assertContains(response, '<main id="info">', status_code=500)
|
||||
self.assertContains(response, '<footer id="explanation">', status_code=500)
|
||||
self.assertContains(
|
||||
response,
|
||||
"<th>Raised during:</th><td>view_tests.views.raises500</td>",
|
||||
'<th scope="row">Raised during:</th><td>view_tests.views.raises500</td>',
|
||||
status_code=500,
|
||||
html=True,
|
||||
)
|
||||
@ -255,7 +267,8 @@ class DebugViewTests(SimpleTestCase):
|
||||
response = self.client.get("/classbased500/")
|
||||
self.assertContains(
|
||||
response,
|
||||
"<th>Raised during:</th><td>view_tests.views.Raises500View</td>",
|
||||
'<th scope="row">Raised during:</th>'
|
||||
"<td>view_tests.views.Raises500View</td>",
|
||||
status_code=500,
|
||||
html=True,
|
||||
)
|
||||
@ -397,7 +410,7 @@ class DebugViewTests(SimpleTestCase):
|
||||
"""
|
||||
response = self.client.get("/")
|
||||
self.assertContains(
|
||||
response, "Page not found <span>(404)</span>", status_code=404
|
||||
response, "Page not found <small>(404)</small>", status_code=404
|
||||
)
|
||||
|
||||
def test_template_encoding(self):
|
||||
@ -531,12 +544,12 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">Can't find my keys</pre>', html
|
||||
)
|
||||
self.assertIn("<th>Request Method:</th>", html)
|
||||
self.assertIn("<th>Request URL:</th>", html)
|
||||
self.assertIn('<th scope="row">Request Method:</th>', html)
|
||||
self.assertIn('<th scope="row">Request URL:</th>', html)
|
||||
self.assertIn('<h3 id="user-info">USER</h3>', html)
|
||||
self.assertIn("<p>jacob</p>", html)
|
||||
self.assertIn("<th>Exception Type:</th>", html)
|
||||
self.assertIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertNotIn("<p>Request data not supplied</p>", html)
|
||||
@ -554,11 +567,11 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">Can't find my keys</pre>', html
|
||||
)
|
||||
self.assertNotIn("<th>Request Method:</th>", html)
|
||||
self.assertNotIn("<th>Request URL:</th>", html)
|
||||
self.assertNotIn('<th scope="row">Request Method:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Request URL:</th>', html)
|
||||
self.assertNotIn('<h3 id="user-info">USER</h3>', html)
|
||||
self.assertIn("<th>Exception Type:</th>", html)
|
||||
self.assertIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertIn("<p>Request data not supplied</p>", html)
|
||||
@ -603,10 +616,10 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">No exception message supplied</pre>', html
|
||||
)
|
||||
self.assertIn("<th>Request Method:</th>", html)
|
||||
self.assertIn("<th>Request URL:</th>", html)
|
||||
self.assertNotIn("<th>Exception Type:</th>", html)
|
||||
self.assertNotIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Request Method:</th>', html)
|
||||
self.assertIn('<th scope="row">Request URL:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertNotIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertNotIn("<p>Request data not supplied</p>", html)
|
||||
@ -626,8 +639,8 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">Can't find my keys</pre>', html
|
||||
)
|
||||
self.assertIn("<th>Exception Type:</th>", html)
|
||||
self.assertIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertIn("<p>Request data not supplied</p>", html)
|
||||
@ -650,8 +663,8 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
html = reporter.get_traceback_html()
|
||||
self.assertInHTML("<h1>RuntimeError</h1>", html)
|
||||
self.assertIn('<pre class="exception_value">Oops</pre>', html)
|
||||
self.assertIn("<th>Exception Type:</th>", html)
|
||||
self.assertIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertIn("<p>Request data not supplied</p>", html)
|
||||
@ -721,8 +734,8 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
html = reporter.get_traceback_html()
|
||||
self.assertInHTML("<h1>RuntimeError</h1>", html)
|
||||
self.assertIn('<pre class="exception_value">Oops</pre>', html)
|
||||
self.assertIn("<th>Exception Type:</th>", html)
|
||||
self.assertIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertInHTML('<li class="frame user">Traceback: None</li>', html)
|
||||
self.assertIn(
|
||||
@ -981,10 +994,10 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">I'm a little teapot</pre>', html
|
||||
)
|
||||
self.assertIn("<th>Request Method:</th>", html)
|
||||
self.assertIn("<th>Request URL:</th>", html)
|
||||
self.assertNotIn("<th>Exception Type:</th>", html)
|
||||
self.assertNotIn("<th>Exception Value:</th>", html)
|
||||
self.assertIn('<th scope="row">Request Method:</th>', html)
|
||||
self.assertIn('<th scope="row">Request URL:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertNotIn("<p>Request data not supplied</p>", html)
|
||||
@ -996,10 +1009,10 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||
self.assertIn(
|
||||
'<pre class="exception_value">I'm a little teapot</pre>', html
|
||||
)
|
||||
self.assertNotIn("<th>Request Method:</th>", html)
|
||||
self.assertNotIn("<th>Request URL:</th>", html)
|
||||
self.assertNotIn("<th>Exception Type:</th>", html)
|
||||
self.assertNotIn("<th>Exception Value:</th>", html)
|
||||
self.assertNotIn('<th scope="row">Request Method:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Request URL:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Type:</th>', html)
|
||||
self.assertNotIn('<th scope="row">Exception Value:</th>', html)
|
||||
self.assertIn("<h2>Traceback ", html)
|
||||
self.assertIn("<h2>Request information</h2>", html)
|
||||
self.assertIn("<p>Request data not supplied</p>", html)
|
||||
|
Loading…
Reference in New Issue
Block a user