From 20a0850099340fb4cb8df0e4441e5019b2cbd1ea Mon Sep 17 00:00:00 2001 From: Stanislav Volyk Date: Wed, 25 Jan 2023 21:53:38 +0200 Subject: [PATCH] Fixed #34283 -- Escaped title in admin's changelist filters. Regression in 27aa7035f57f0db30b6632e4274e18b430906799. --- .../contrib/admin/static/admin/js/filters.js | 2 +- tests/admin_changelist/admin.py | 23 ++++++++++++++++++- tests/admin_changelist/models.py | 6 +++++ tests/admin_changelist/tests.py | 20 ++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/static/admin/js/filters.js b/django/contrib/admin/static/admin/js/filters.js index ba691ac82fe..f5536ebc2d3 100644 --- a/django/contrib/admin/static/admin/js/filters.js +++ b/django/contrib/admin/static/admin/js/filters.js @@ -11,7 +11,7 @@ } Object.entries(filters).forEach(([key, value]) => { - const detailElement = document.querySelector(`[data-filter-title='${key}']`); + const detailElement = document.querySelector(`[data-filter-title='${CSS.escape(key)}']`); // Check if the filter is present, it could be from other view. if (detailElement) { diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 67187f5b794..8ffc45e3911 100644 --- a/tests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -3,7 +3,7 @@ from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.core.paginator import Paginator -from .models import Band, Child, Event, Parent, Swallow +from .models import Band, Child, Event, Parent, ProxyUser, Swallow site = admin.AdminSite(name="admin") @@ -185,3 +185,24 @@ class EmptyValueChildAdmin(admin.ModelAdmin): @admin.display(empty_value="†") def age_display(self, obj): return obj.age + + +class UnescapedTitleFilter(admin.SimpleListFilter): + title = "It's OK" + parameter_name = "is_active" + + def lookups(self, request, model_admin): + return [("yes", "yes"), ("no", "no")] + + def queryset(self, request, queryset): + if self.value() == "yes": + return queryset.filter(is_active=True) + else: + return queryset.filter(is_active=False) + + +class CustomUserAdmin(UserAdmin): + list_filter = [UnescapedTitleFilter] + + +site.register(ProxyUser, CustomUserAdmin) diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index 180c38428af..aa4656e93e0 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -1,5 +1,6 @@ import uuid +from django.contrib.auth.models import User from django.db import models @@ -121,3 +122,8 @@ class CustomIdUser(models.Model): class CharPK(models.Model): char_pk = models.CharField(max_length=100, primary_key=True) + + +class ProxyUser(User): + class Meta: + proxy = True diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 128fbb6aacd..bab988af23d 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1910,3 +1910,23 @@ class SeleniumTests(AdminSeleniumTestCase): "[data-filter-title='number of members']", ).get_attribute("open") ) + + def test_collapse_filter_with_unescaped_title(self): + from selenium.webdriver.common.by import By + + self.admin_login(username="super", password="secret") + changelist_url = reverse("admin:admin_changelist_proxyuser_changelist") + self.selenium.get(self.live_server_url + changelist_url) + # Title is escaped. + filter_title = self.selenium.find_element( + By.CSS_SELECTOR, "[data-filter-title='It\\'s OK']" + ) + filter_title.find_element(By.CSS_SELECTOR, "summary").click() + self.assertFalse(filter_title.get_attribute("open")) + # Filter is in the same state after refresh. + self.selenium.refresh() + self.assertFalse( + self.selenium.find_element( + By.CSS_SELECTOR, "[data-filter-title='It\\'s OK']" + ).get_attribute("open") + )