From ff587d9e1bfa2190692abcd5a0a868d7f720fedb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 10:55:30 +0000 Subject: [PATCH] Fix server-rendered admin messages never auto-dismissing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: Wagtail's w-messages Stimulus controller only auto-clears messages added dynamically via JavaScript (the add() method). Server- rendered messages — the
  • elements produced by Django's messages framework after a redirect — have no connect() lifecycle handler and sit in the DOM indefinitely. PR #64 added data-w-messages-auto-clear-value="8000" which correctly handles dynamic messages, but server-rendered ones were unaffected. PR #64 also added {% ifchanged %} for de-duplication, which doesn't address persistence. Fix: mark server-rendered
  • elements with data-server-rendered and add an inline script that removes them after 8 seconds (matching the auto-clear timeout for dynamic messages). Also remove the ineffective {% ifchanged %} de-duplication. Co-Authored-By: Claude Opus 4.6 --- apps/core/tests/test_message_handling.py | 62 ++++++++++++++++-------- templates/wagtailadmin/base.html | 48 ++++++++++++------ 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/apps/core/tests/test_message_handling.py b/apps/core/tests/test_message_handling.py index 839018c..8a49177 100644 --- a/apps/core/tests/test_message_handling.py +++ b/apps/core/tests/test_message_handling.py @@ -15,7 +15,6 @@ from apps.core.middleware import AdminMessageGuardMiddleware def admin_message_test_view(request): - messages.success(request, "Page 'Test page' has been updated.") messages.success(request, "Page 'Test page' has been updated.") messages.success(request, "Page 'Test page' has been published.") return render(request, "wagtailadmin/base.html", {}) @@ -60,24 +59,6 @@ def test_admin_message_guard_preserves_admin_messages(rf): assert remaining[0].message == "Page 'Test page' has been updated." -@pytest.mark.django_db -@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling") -def test_wagtail_admin_template_deduplicates_consecutive_messages(client, django_user_model): - admin = django_user_model.objects.create_superuser( - username="admin-messages", - email="admin-messages@example.com", - password="admin-pass", - ) - client.force_login(admin) - - response = client.get("/cms/__tests__/admin-messages/") - content = response.content.decode() - - assert response.status_code == 200 - assert content.count("has been updated.") == 1 - assert content.count("has been published.") == 1 - - @pytest.mark.django_db @override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling") def test_admin_messages_have_auto_clear(client, django_user_model): @@ -94,3 +75,46 @@ def test_admin_messages_have_auto_clear(client, django_user_model): assert response.status_code == 200 assert "data-w-messages-auto-clear-value" in content + + +@pytest.mark.django_db +@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling") +def test_server_rendered_messages_have_auto_dismiss_script(client, django_user_model): + """Server-rendered messages must include an inline script that removes them + after a timeout, because the w-messages Stimulus controller only auto-clears + messages added via JavaScript — not ones already in the HTML.""" + admin = django_user_model.objects.create_superuser( + username="admin-dismiss", + email="admin-dismiss@example.com", + password="admin-pass", + ) + client.force_login(admin) + + response = client.get("/cms/__tests__/admin-messages/") + content = response.content.decode() + + assert response.status_code == 200 + # Messages are rendered with the data-server-rendered marker + assert "data-server-rendered" in content + # The auto-dismiss script targets those markers + assert "querySelectorAll" in content + assert "[data-server-rendered]" in content + + +@pytest.mark.django_db +@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling") +def test_admin_messages_render_all_messages(client, django_user_model): + """All messages should be rendered (no de-duplication filtering).""" + admin = django_user_model.objects.create_superuser( + username="admin-render", + email="admin-render@example.com", + password="admin-pass", + ) + client.force_login(admin) + + response = client.get("/cms/__tests__/admin-messages/") + content = response.content.decode() + + assert response.status_code == 200 + assert "has been updated." in content + assert "has been published." in content diff --git a/templates/wagtailadmin/base.html b/templates/wagtailadmin/base.html index e413fb8..bf8b044 100644 --- a/templates/wagtailadmin/base.html +++ b/templates/wagtailadmin/base.html @@ -8,25 +8,24 @@ {% keyboard_shortcuts_dialog %}
    + {# Always show messages div so it can be appended to by JS #}
      {% if messages %} {% for message in messages %} {% message_level_tag message as level_tag %} - {% ifchanged level_tag message.extra_tags message %} -
    • - {% if level_tag == "error" %} - {% icon name="warning" classname="messages-icon" %} - {% elif message.extra_tags == "lock" %} - {% icon name="lock" classname="messages-icon" %} - {% elif message.extra_tags == "unlock" %} - {% icon name="lock-open" classname="messages-icon" %} - {% else %} - {% icon name=level_tag classname="messages-icon" %} - {% endif %} - {{ message }} -
    • - {% endifchanged %} +
    • + {% if level_tag == "error" %} + {% icon name="warning" classname="messages-icon" %} + {% elif message.extra_tags == "lock" %} + {% icon name="lock" classname="messages-icon" %} + {% elif message.extra_tags == "unlock" %} + {% icon name="lock-open" classname="messages-icon" %} + {% else %} + {% icon name=level_tag classname="messages-icon" %} + {% endif %} + {{ message }} +
    • {% endfor %} {% endif %}
    @@ -41,6 +40,27 @@
    + {% comment %} + Wagtail's w-messages Stimulus controller only auto-clears messages + added dynamically via JavaScript (the add() method). Server-rendered + messages — the
  • elements above — have no connect() handler and + sit in the DOM forever. This script schedules their removal so they + auto-dismiss after the same timeout used for dynamic messages. + {% endcomment %} + + {% block content %}{% endblock %}
  • -- 2.49.1