diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html
index 6801fe5fa7..c9a494dc19 100644
--- a/django/contrib/admin/templates/admin/auth/user/change_password.html
+++ b/django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -2,6 +2,7 @@
{% load i18n static %}
{% load admin_urls %}
+{% block title %}{% if form.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}
{{ block.super }}
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 09ef954e5c..b9bde30018 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
+{% block title %}{% if errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrahead %}{{ block.super }}
{{ media }}
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 310872b015..fa189102bb 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_list %}
+{% block title %}{% if cl.formset and cl.formset.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}
{{ block.super }}
diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html
index b61d9ec603..8c2c7d11ff 100644
--- a/django/contrib/admin/templates/admin/login.html
+++ b/django/contrib/admin/templates/admin/login.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
+{% block title %}{% if form.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}{{ block.super }}
{{ form.media }}
{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html
index fde2373e08..20c78210c6 100644
--- a/django/contrib/admin/templates/registration/password_change_form.html
+++ b/django/contrib/admin/templates/registration/password_change_form.html
@@ -1,5 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
+
+{% block title %}{% if form.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}{{ block.super }}{% endblock %}
{% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}{% translate 'Documentation' %} / {% endif %} {% translate 'Change password' %} /
diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html
index a07645c97a..3866b5aead 100644
--- a/django/contrib/admin/templates/registration/password_reset_confirm.html
+++ b/django/contrib/admin/templates/registration/password_reset_confirm.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
+{% block title %}{% if form.new_password1.errors or form.new_password2.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}{{ block.super }}{% endblock %}
{% block breadcrumbs %}
diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html
index 0edfea8ec2..7200e0f8be 100644
--- a/django/contrib/admin/templates/registration/password_reset_form.html
+++ b/django/contrib/admin/templates/registration/password_reset_form.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
+{% block title %}{% if form.email.errors %}{{ _('Error:') }} {% endif %}{{ block.super }}{% endblock %}
{% block extrastyle %}{{ block.super }}
{% endblock %}
{% block breadcrumbs %}
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index ec6820c62f..694f807781 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -1264,6 +1264,24 @@ class ChangeListTests(TestCase):
# Check only the first few characters since the UUID may have dashes.
self.assertIn(str(a.pk)[:8], context.captured_queries[4]["sql"])
+ def test_list_editable_error_title(self):
+ a = Swallow.objects.create(origin="Swallow A", load=4, speed=1)
+ Swallow.objects.create(origin="Swallow B", load=2, speed=2)
+ data = {
+ "form-TOTAL_FORMS": "2",
+ "form-INITIAL_FORMS": "2",
+ "form-MIN_NUM_FORMS": "0",
+ "form-MAX_NUM_FORMS": "1000",
+ "form-0-uuid": str(a.pk),
+ "form-0-load": "invalid",
+ "_save": "Save",
+ }
+ superuser = self._create_superuser("superuser")
+ self.client.force_login(superuser)
+ changelist_url = reverse("admin:admin_changelist_swallow_changelist")
+ response = self.client.post(changelist_url, data=data)
+ self.assertContains(response, "Error: Select swallow to change")
+
def test_deterministic_order_for_unordered_model(self):
"""
The primary key is used in the ordering of the changelist's results to
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 9a031a1e51..fc1bb86d85 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -1508,6 +1508,24 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
self.assertContains(response, "
Change article
")
self.assertContains(response, "Article 2
")
+ def test_error_in_titles(self):
+ for url, subtitle in [
+ (
+ reverse("admin:admin_views_article_change", args=(self.a1.pk,)),
+ "Article 1 | Change article",
+ ),
+ (reverse("admin:admin_views_article_add"), "Add article"),
+ (reverse("admin:login"), "Log in"),
+ (reverse("admin:password_change"), "Password change"),
+ (
+ reverse("admin:auth_user_password_change", args=(self.superuser.id,)),
+ "Change password: super",
+ ),
+ ]:
+ with self.subTest(url=url, subtitle=subtitle):
+ response = self.client.post(url, {})
+ self.assertContains(response, f"Error: {subtitle}")
+
def test_view_subtitle_per_object(self):
viewuser = User.objects.create_user(
username="viewuser",
diff --git a/tests/auth_tests/test_templates.py b/tests/auth_tests/test_templates.py
index ceecfa3325..edde6ca6b4 100644
--- a/tests/auth_tests/test_templates.py
+++ b/tests/auth_tests/test_templates.py
@@ -37,6 +37,12 @@ class AuthTemplateTests(TestCase):
)
self.assertContains(response, "Password reset
")
+ def test_password_reset_view_error_title(self):
+ response = self.client.post(reverse("password_reset"), {})
+ self.assertContains(
+ response, "Error: Password reset | Django site admin"
+ )
+
def test_password_reset_done_view(self):
response = PasswordResetDoneView.as_view()(self.request)
self.assertContains(
@@ -77,6 +83,19 @@ class AuthTemplateTests(TestCase):
'',
)
+ def test_password_reset_confirm_view_error_title(self):
+ client = PasswordResetConfirmClient()
+ default_token_generator = PasswordResetTokenGenerator()
+ token = default_token_generator.make_token(self.user)
+ uidb64 = urlsafe_base64_encode(str(self.user.pk).encode())
+ url = reverse(
+ "password_reset_confirm", kwargs={"uidb64": uidb64, "token": token}
+ )
+ response = client.post(url, {})
+ self.assertContains(
+ response, "Error: Enter new password | Django site admin"
+ )
+
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUser")
def test_password_reset_confirm_view_custom_username_hint(self):
custom_user = CustomUser.custom_objects.create_user(