[Django] #36541: Using the `string_if_invalid` template configuration breaks the password reset button in the `UserAdmin`

9 views
Skip to first unread message

Django

unread,
Aug 5, 2025, 1:01:04 PMAug 5
to django-...@googlegroups.com
#36541: Using the `string_if_invalid` template configuration breaks the password
reset button in the `UserAdmin`
------------------------------+-----------------------------------------
Reporter: Drew Winstel | Type: Uncategorized
Status: new | Component: Uncategorized
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+-----------------------------------------
Steps to replicate:

1. Create any basic app (polls is fine) and add `path("admin/",
admin.site.urls)` to your urlconf.
2. Set this template configuration in your settings.py:
{{{
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"OPTIONS": {
"string_if_invalid": "INVALID EXPRESSION: %s",
},
},
]
}}}
3. Navigate to the user detail view in the admin for any user
4. Observe that the Reset password button renders to HTML as `<a
class="button" href="INVALID EXPRESSION: password_url">Reset password</a>`
which returns a 404 if you click on the button

This is because the default
[https://github.com/django/django/blob/stable/5.2.x/django/contrib/auth/templates/auth/widgets/read_only_password_hash.html#L7
template for the password reset button] looks for the `password_url`
template context, which isn't set at all by default. However, when you
have `string_if_invalid` set, `password_url` resolves to the fallback
string, preventing the `default` filter from returning the correct value.

There are two workarounds:
1. Find a way to inject a `password_url` into your context that gets set
in the context
2. Override the `read_only_password_hash.html` template locally to hard-
code the link to point to `../password/`
--
Ticket URL: <https://code.djangoproject.com/ticket/36541>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Aug 5, 2025, 3:05:34 PMAug 5
to django-...@googlegroups.com
#36541: Using the `string_if_invalid` template configuration breaks the password
reset button in the `UserAdmin`
-------------------------------------+-------------------------------------
Reporter: Drew Winstel | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: contrib.auth | Version: 5.2
Severity: Normal | Resolution:
| worksforme
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Natalia Bidart):

* component: Uncategorized => contrib.auth
* resolution: => worksforme
* status: new => closed
* type: Uncategorized => Cleanup/optimization

Comment:

Hello Drew! Thank you for taking the time to create this ticket. I see
your point and I agree that not having a `password_url` defined is an
issue we could/should improve. But I have not been able to reproduce what
you see. See my two attempts below:

* I have defined a simple `TEMPLATES` settings like this:
{{{#!python
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"OPTIONS": {
"string_if_invalid": "INVALID EXPRESSION: %s",
"loaders": [
"django.template.loaders.app_directories.Loader",
],
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]
},
},
]
}}}
And while I do see the `string_if_invalid` used in my app templates, it's
not used in the admin templates. I debugged for a while and I see that the
engine being used to render the admin has a slightly different definition
of the one used for my site templates. Could you please the exact setting
definition that you are using?

* I also wrote this test which is passing for me (i.e. no `INVALID STRING`
is printed in the password URL):
{{{#!diff
diff --git a/tests/auth_tests/test_views.py
b/tests/auth_tests/test_views.py
index 0a4f7d28c8..ea1632d82d 100644
--- a/tests/auth_tests/test_views.py
+++ b/tests/auth_tests/test_views.py
@@ -1617,6 +1617,26 @@ class ChangelistTests(MessagesTestMixin,
AuthViewsTestCase):
self.admin.refresh_from_db()
self.assertIs(self.admin.has_usable_password(), False)

+ @override_settings(TEMPLATES=[
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "OPTIONS": {
+ "string_if_invalid": "INVALID STRING: %s",
+ "loaders": [
+ "django.template.loaders.app_directories.Loader",
+ ],
+ },
+ },
+ ])
+ def
test_user_with_usable_password_change_password_string_if_invalid(self):
+ user_change_url = reverse(
+ "auth_test_admin:auth_user_change", args=(self.admin.pk,)
+ )
+ response = self.client.get(user_change_url)
+ # Test the link inside password field help_text.
+ expected = '<a role="button" class="button"
href="../password/">Reset password</a>'
+ self.assertContains(response, expected, html=True)
+
}}}

I'll close as `worksforme` for now, but please reopen when you can provide
further details or a way to reproduce. Thanks again!
--
Ticket URL: <https://code.djangoproject.com/ticket/36541#comment:1>

Django

unread,
Aug 5, 2025, 3:22:37 PMAug 5
to django-...@googlegroups.com
#36541: Using the `string_if_invalid` template configuration breaks the password
reset button in the `UserAdmin`
-------------------------------------+-------------------------------------
Reporter: Drew Winstel | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: contrib.auth | Version: 5.2
Severity: Normal | Resolution:
| worksforme
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Drew Winstel):

Thanks, Natalia! I'll do some more digging and see if I can get a full
repro case up.
--
Ticket URL: <https://code.djangoproject.com/ticket/36541#comment:2>
Reply all
Reply to author
Forward
0 new messages