diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index ae697cf58d..c6ce78833e 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -169,6 +169,10 @@ form .aligned select + div.help { padding-left: 10px; } +form .aligned select option:checked { + background-color: var(--selected-row); +} + form .aligned ul li { list-style: none; } diff --git a/docs/releases/5.1.2.txt b/docs/releases/5.1.2.txt index 2727b112dc..a7293654df 100644 --- a/docs/releases/5.1.2.txt +++ b/docs/releases/5.1.2.txt @@ -16,3 +16,7 @@ Bugfixes * Fixed a regression in Django 5.1 that caused a crash of ``JSONObject()`` when using server-side binding with PostgreSQL 16+ (:ticket:`35734`). + +* Fixed a regression in Django 5.1 that made selected items in multi-select + widgets indistinguishable from non-selected items in the admin dark theme + (:ticket:`35809`). diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 16d76f5da2..f63a9ca56f 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -6175,6 +6175,65 @@ class SeleniumTests(AdminSeleniumTestCase): ) self.take_screenshot("selectbox-non-collapsible") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) + def test_selectbox_selected_rows(self): + from selenium.webdriver import ActionChains + from selenium.webdriver.common.by import By + from selenium.webdriver.common.keys import Keys + + self.admin_login( + username="super", password="secret", login_url=reverse("admin:index") + ) + # Create a new user to ensure that no extra permissions have been set. + user = User.objects.create_user(username="new", password="newuser") + url = self.live_server_url + reverse("admin:auth_user_change", args=[user.id]) + self.selenium.get(url) + + # Scroll to the User permissions section. + user_permissions = self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_from" + ) + ActionChains(self.selenium).move_to_element(user_permissions).perform() + self.take_screenshot("selectbox-available-perms-none-selected") + + # Select multiple permissions from the "Available" list. + ct = ContentType.objects.get_for_model(Permission) + perms = list(Permission.objects.filter(content_type=ct)) + for perm in perms: + elem = self.selenium.find_element( + By.CSS_SELECTOR, f"#id_user_permissions_from option[value='{perm.id}']" + ) + ActionChains(self.selenium).key_down(Keys.CONTROL).click(elem).key_up( + Keys.CONTROL + ).perform() + + # Move focus to other element. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_input" + ).click() + self.take_screenshot("selectbox-available-perms-some-selected") + + # Move permissions to the "Chosen" list, but none is selected yet. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_add_link" + ).click() + self.take_screenshot("selectbox-chosen-perms-none-selected") + + # Select some permissions from the "Chosen" list. + for perm in [perms[0], perms[-1]]: + elem = self.selenium.find_element( + By.CSS_SELECTOR, f"#id_user_permissions_to option[value='{perm.id}']" + ) + ActionChains(self.selenium).key_down(Keys.CONTROL).click(elem).key_up( + Keys.CONTROL + ).perform() + + # Move focus to other element. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_selected_input" + ).click() + self.take_screenshot("selectbox-chosen-perms-some-selected") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) def test_first_field_focus(self): """JavaScript-assisted auto-focus on first usable form field."""