瀏覽代碼

Fixed #35940 -- Disabled SelectFilter add/remove buttons when appropriate.

Brock 3 月之前
父節點
當前提交
8c118c0e00

+ 2 - 2
django/contrib/admin/static/admin/css/responsive.css

@@ -631,7 +631,7 @@ input[type="submit"], button {
         background-position: 0 0;
     }
 
-    .active.selector-remove:focus, .active.selector-remove:hover {
+    :enabled.selector-remove:focus, :enabled.selector-remove:hover {
         background-position: 0 -24px;
     }
 
@@ -639,7 +639,7 @@ input[type="submit"], button {
         background-position: 0 -48px;
     }
 
-    .active.selector-add:focus, .active.selector-add:hover {
+    :enabled.selector-add:focus, :enabled.selector-add:hover {
         background-position: 0 -72px;
     }
 

+ 2 - 2
django/contrib/admin/static/admin/css/responsive_rtl.css

@@ -75,7 +75,7 @@
         background-position: 0 0;
     }
 
-    [dir="rtl"] .active.selector-remove:focus, .active.selector-remove:hover {
+    [dir="rtl"] :enabled.selector-remove:focus, :enabled.selector-remove:hover {
         background-position: 0 -24px;
     }
 
@@ -83,7 +83,7 @@
         background-position: 0 -48px;
     }
 
-    [dir="rtl"] .active.selector-add:focus, .active.selector-add:hover {
+    [dir="rtl"] :enabled.selector-add:focus, :enabled.selector-add:hover {
         background-position: 0 -72px;
     }
 }

+ 4 - 4
django/contrib/admin/static/admin/css/rtl.css

@@ -224,7 +224,7 @@ fieldset .fieldBox {
     background-size: 24px auto;
 }
 
-.active.selector-add:focus, .active.selector-add:hover {
+:enabled.selector-add:focus, :enabled.selector-add:hover {
     background-position: 0 -120px;
 }
 
@@ -233,7 +233,7 @@ fieldset .fieldBox {
     background-size: 24px auto;
 }
 
-.active.selector-remove:focus, .active.selector-remove:hover {
+:enabled.selector-remove:focus, :enabled.selector-remove:hover {
     background-position: 0 -168px;
 }
 
@@ -241,7 +241,7 @@ fieldset .fieldBox {
     background: url(../img/selector-icons.svg) right -128px no-repeat;
 }
 
-.active.selector-chooseall:focus, .active.selector-chooseall:hover {
+:enabled.selector-chooseall:focus, :enabled.selector-chooseall:hover {
     background-position: 100% -144px;
 }
 
@@ -249,7 +249,7 @@ fieldset .fieldBox {
     background: url(../img/selector-icons.svg) 0 -160px no-repeat;
 }
 
-.active.selector-clearall:focus, .active.selector-clearall:hover {
+:enabled.selector-clearall:focus, :enabled.selector-clearall:hover {
     background-position: 0 -176px;
 }
 

+ 14 - 14
django/contrib/admin/static/admin/css/widgets.css

@@ -129,11 +129,11 @@
     border: none;
 }
 
-.active.selector-add, .active.selector-remove {
+:enabled.selector-add, :enabled.selector-remove {
     opacity: 1;
 }
 
-.active.selector-add:hover, .active.selector-remove:hover {
+:enabled.selector-add:hover, :enabled.selector-remove:hover {
     cursor: pointer;
 }
 
@@ -142,7 +142,7 @@
     background-size: 24px auto;
 }
 
-.active.selector-add:focus, .active.selector-add:hover {
+:enabled.selector-add:focus, :enabled.selector-add:hover {
     background-position: 0 -168px;
 }
 
@@ -151,7 +151,7 @@
     background-size: 24px auto;
 }
 
-.active.selector-remove:focus, .active.selector-remove:hover {
+:enabled.selector-remove:focus, :enabled.selector-remove:hover {
     background-position: 0 -120px;
 }
 
@@ -169,16 +169,16 @@
     border: none;
 }
 
-.active.selector-chooseall:focus, .active.selector-clearall:focus,
-.active.selector-chooseall:hover, .active.selector-clearall:hover {
+:enabled.selector-chooseall:focus, :enabled.selector-clearall:focus,
+:enabled.selector-chooseall:hover, :enabled.selector-clearall:hover {
     color: var(--link-fg);
 }
 
-.active.selector-chooseall, .active.selector-clearall {
+:enabled.selector-chooseall, :enabled.selector-clearall {
     opacity: 1;
 }
 
-.active.selector-chooseall:hover, .active.selector-clearall:hover {
+:enabled.selector-chooseall:hover, :enabled.selector-clearall:hover {
     cursor: pointer;
 }
 
@@ -188,7 +188,7 @@
     cursor: default;
 }
 
-.active.selector-chooseall:focus, .active.selector-chooseall:hover {
+:enabled.selector-chooseall:focus, :enabled.selector-chooseall:hover {
     background-position: 100% -176px;
 }
 
@@ -198,7 +198,7 @@
     cursor: default;
 }
 
-.active.selector-clearall:focus, .active.selector-clearall:hover {
+:enabled.selector-clearall:focus, :enabled.selector-clearall:hover {
     background-position: 0 -144px;
 }
 
@@ -252,12 +252,12 @@
     cursor: default;
 }
 
-.stacked .active.selector-add {
+.stacked :enabled.selector-add {
     background-position: 0 -48px;
     cursor: pointer;
 }
 
-.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
+.stacked :enabled.selector-add:focus, .stacked :enabled.selector-add:hover {
     background-position: 0 -72px;
     cursor: pointer;
 }
@@ -268,12 +268,12 @@
     cursor: default;
 }
 
-.stacked .active.selector-remove {
+.stacked :enabled.selector-remove {
     background-position: 0 0px;
     cursor: pointer;
 }
 
-.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
+.stacked :enabled.selector-remove:focus, .stacked :enabled.selector-remove:hover {
     background-position: 0 -24px;
     cursor: pointer;
 }

+ 7 - 8
django/contrib/admin/static/admin/js/SelectFilter2.js

@@ -149,7 +149,7 @@ Requires core.js and SelectBox.js.
 
             // Set up the JavaScript event handlers for the select box filter interface
             const move_selection = function(e, elem, move_func, from, to) {
-                if (elem.classList.contains('active')) {
+                if (!elem.hasAttribute('disabled')) {
                     move_func(from, to);
                     SelectFilter.refresh_icons(field_id);
                     SelectFilter.refresh_filtered_selects(field_id);
@@ -248,13 +248,12 @@ Requires core.js and SelectBox.js.
         refresh_icons: function(field_id) {
             const from = document.getElementById(field_id + '_from');
             const to = document.getElementById(field_id + '_to');
-            // Active if at least one item is selected
-            document.getElementById(field_id + '_add').classList.toggle('active', SelectFilter.any_selected(from));
-            document.getElementById(field_id + '_remove').classList.toggle('active', SelectFilter.any_selected(to));
-            // Active if the corresponding box isn't empty
-            document.getElementById(field_id + '_add_all').classList.toggle('active', from.querySelector('option'));
-            document.getElementById(field_id + '_remove_all').classList.toggle('active', to.querySelector('option'));
-            SelectFilter.refresh_filtered_warning(field_id);
+            // Disabled if no items are selected.
+            document.getElementById(field_id + '_add').disabled = !SelectFilter.any_selected(from);
+            document.getElementById(field_id + '_remove').disabled = !SelectFilter.any_selected(to);
+            // Disabled if the corresponding box is empty.
+            document.getElementById(field_id + '_add_all').disabled = !from.querySelector('option');
+            document.getElementById(field_id + '_remove_all').disabled = !to.querySelector('option');
         },
         filter_key_press: function(event, field_id, source, target) {
             const source_box = document.getElementById(field_id + source);

+ 6 - 9
django/contrib/admin/tests.py

@@ -218,19 +218,16 @@ class AdminSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase):
         """
         self._assertOptionsValues("%s > option:checked" % selector, values)
 
-    def has_css_class(self, selector, klass):
+    def is_disabled(self, selector):
         """
-        Return True if the element identified by `selector` has the CSS class
-        `klass`.
+        Return True if the element identified by `selector` has the `disabled`
+        attribute.
         """
         from selenium.webdriver.common.by import By
 
         return (
-            self.selenium.find_element(
-                By.CSS_SELECTOR,
-                selector,
+            self.selenium.find_element(By.CSS_SELECTOR, selector).get_attribute(
+                "disabled"
             )
-            .get_attribute("class")
-            .find(klass)
-            != -1
+            == "true"
         )

+ 68 - 13
tests/admin_widgets/tests.py

@@ -1254,21 +1254,27 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
         self.arthur = Student.objects.create(name="Arthur")
         self.school = School.objects.create(name="School of Awesome")
 
-    def assertActiveButtons(
-        self, mode, field_name, choose, remove, choose_all=None, remove_all=None
+    def assertButtonsDisabled(
+        self,
+        mode,
+        field_name,
+        choose_btn_disabled=False,
+        remove_btn_disabled=False,
+        choose_all_btn_disabled=False,
+        remove_all_btn_disabled=False,
     ):
         choose_button = "#id_%s_add" % field_name
         choose_all_button = "#id_%s_add_all" % field_name
         remove_button = "#id_%s_remove" % field_name
         remove_all_button = "#id_%s_remove_all" % field_name
-        self.assertEqual(self.has_css_class(choose_button, "active"), choose)
-        self.assertEqual(self.has_css_class(remove_button, "active"), remove)
+        self.assertEqual(self.is_disabled(choose_button), choose_btn_disabled)
+        self.assertEqual(self.is_disabled(remove_button), remove_btn_disabled)
         if mode == "horizontal":
             self.assertEqual(
-                self.has_css_class(choose_all_button, "active"), choose_all
+                self.is_disabled(choose_all_button), choose_all_btn_disabled
             )
             self.assertEqual(
-                self.has_css_class(remove_all_button, "active"), remove_all
+                self.is_disabled(remove_all_button), remove_all_btn_disabled
             )
 
     def execute_basic_operations(self, mode, field_name):
@@ -1296,7 +1302,14 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
             ],
         )
         self.assertSelectOptions(to_box, [str(self.lisa.id), str(self.peter.id)])
-        self.assertActiveButtons(mode, field_name, False, False, True, True)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=False,
+        )
 
         # Click 'Choose all' --------------------------------------------------
         if mode == "horizontal":
@@ -1323,7 +1336,14 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
                 str(self.john.id),
             ],
         )
-        self.assertActiveButtons(mode, field_name, False, False, False, True)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=True,
+            remove_all_btn_disabled=False,
+        )
 
         # Click 'Remove all' --------------------------------------------------
         if mode == "horizontal":
@@ -1350,7 +1370,14 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
             ],
         )
         self.assertSelectOptions(to_box, [])
-        self.assertActiveButtons(mode, field_name, False, False, True, False)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=True,
+        )
 
         # Choose some options ------------------------------------------------
         from_lisa_select_option = self.selenium.find_element(
@@ -1367,9 +1394,23 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
         self.select_option(from_box, str(self.jason.id))
         self.select_option(from_box, str(self.bob.id))
         self.select_option(from_box, str(self.john.id))
-        self.assertActiveButtons(mode, field_name, True, False, True, False)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=False,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=True,
+        )
         self.selenium.find_element(By.ID, choose_button).click()
-        self.assertActiveButtons(mode, field_name, False, False, True, True)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=False,
+        )
 
         self.assertSelectOptions(
             from_box,
@@ -1402,9 +1443,23 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
         # Remove some options -------------------------------------------------
         self.select_option(to_box, str(self.lisa.id))
         self.select_option(to_box, str(self.bob.id))
-        self.assertActiveButtons(mode, field_name, False, True, True, True)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=False,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=False,
+        )
         self.selenium.find_element(By.ID, remove_button).click()
-        self.assertActiveButtons(mode, field_name, False, False, True, True)
+        self.assertButtonsDisabled(
+            mode,
+            field_name,
+            choose_btn_disabled=True,
+            remove_btn_disabled=True,
+            choose_all_btn_disabled=False,
+            remove_all_btn_disabled=False,
+        )
 
         self.assertSelectOptions(
             from_box,