summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlayze <blayze@carstickers.com>2025-05-28 11:22:29 -0700
committernessita <124304+nessita@users.noreply.github.com>2025-06-02 22:25:14 -0300
commit90429625a85f1f77dfea200c91bd2dabab57974f (patch)
tree7027e17e658c3aab80c004d64178079a53400130
parent1a744343999c9646912cee76ba0a2fa6ef5e6240 (diff)
Fixed #36423 -- Prevented filter_horizontal buttons from intercepting form submission.
In the admin's filter_horizontal widget, optional action buttons like "Choose all", "Remove all", etc. were changed from `<a>` to `<button>` elements in #34619, but without specifying `type="button"`. As a result, when pressing Enter while focused on a form input, these buttons could be triggered and intercept form submission. Explicitly set `type="button"` on these control buttons to prevent them from acting as submit buttons. Thanks Antoliny Lee for the quick triage and review. Regression in 857b1048d53ebf5fc5581c110e85c212b81ca83a.
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/admin/static/admin/js/SelectFilter2.js12
-rw-r--r--docs/releases/5.2.2.txt4
-rw-r--r--js_tests/admin/SelectFilter2.test.js1
-rw-r--r--tests/admin_widgets/tests.py42
5 files changed, 56 insertions, 4 deletions
diff --git a/AUTHORS b/AUTHORS
index a3adeceae1..f492b36357 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -164,6 +164,7 @@ answer newbie questions, and generally made Django that much better:
Bhuvnesh Sharma <bhuvnesh875@gmail.com>
Bill Fenner <fenner@gmail.com>
Bjørn Stabell <bjorn@exoweb.net>
+ Blayze Wilhelm <https://github.com/blayzen-w>
Bo Marchman <bo.marchman@gmail.com>
Bogdan Mateescu
Bojan Mihelac <bmihelac@mihelac.org>
diff --git a/django/contrib/admin/static/admin/js/SelectFilter2.js b/django/contrib/admin/static/admin/js/SelectFilter2.js
index 08d47fc298..970b511b0c 100644
--- a/django/contrib/admin/static/admin/js/SelectFilter2.js
+++ b/django/contrib/admin/static/admin/js/SelectFilter2.js
@@ -72,7 +72,8 @@ Requires core.js and SelectBox.js.
selector_available,
interpolate(gettext('Choose all %s'), [field_name]),
'id', field_id + '_add_all',
- 'class', 'selector-chooseall'
+ 'class', 'selector-chooseall',
+ 'type', 'button'
);
// <ul class="selector-chooser">
@@ -83,14 +84,16 @@ Requires core.js and SelectBox.js.
quickElement('li', selector_chooser),
interpolate(gettext('Choose selected %s'), [field_name]),
'id', field_id + '_add',
- 'class', 'selector-add'
+ 'class', 'selector-add',
+ 'type', 'button'
);
const remove_button = quickElement(
'button',
quickElement('li', selector_chooser),
interpolate(gettext('Remove selected %s'), [field_name]),
'id', field_id + '_remove',
- 'class', 'selector-remove'
+ 'class', 'selector-remove',
+ 'type', 'button'
);
// <div class="selector-chosen">
@@ -142,7 +145,8 @@ Requires core.js and SelectBox.js.
selector_chosen,
interpolate(gettext('Remove all %s'), [field_name]),
'id', field_id + '_remove_all',
- 'class', 'selector-clearall'
+ 'class', 'selector-clearall',
+ 'type', 'button'
);
from_box.name = from_box.name + '_old';
diff --git a/docs/releases/5.2.2.txt b/docs/releases/5.2.2.txt
index 8ed0856352..5647c0cc07 100644
--- a/docs/releases/5.2.2.txt
+++ b/docs/releases/5.2.2.txt
@@ -30,3 +30,7 @@ Bugfixes
* Fixed a regression in Django 5.2 that caused a crash when using ``OuterRef``
in PostgreSQL aggregate functions ``ArrayAgg``, ``StringAgg``, and
``JSONBAgg`` (:ticket:`36405`).
+
+* Fixed a regression in Django 5.2 where admin's ``filter_horizontal`` buttons
+ lacked ``type="button"``, causing them to intercept form submission when
+ pressing the Enter key (:ticket:`36423`).
diff --git a/js_tests/admin/SelectFilter2.test.js b/js_tests/admin/SelectFilter2.test.js
index 8c1480d2a6..1fd46bd0ce 100644
--- a/js_tests/admin/SelectFilter2.test.js
+++ b/js_tests/admin/SelectFilter2.test.js
@@ -31,6 +31,7 @@ QUnit.test('init', function(assert) {
assert.equal($('.selector-chosen .selector-chosen-title .helptext').text(), 'Remove things by selecting them and then select the "Remove" arrow button.');
assert.equal($('.selector-filter label .help-tooltip')[0].getAttribute("aria-label"), "Type into this box to filter down the list of available things.");
assert.equal($('.selector-filter label .help-tooltip')[1].getAttribute("aria-label"), "Type into this box to filter down the list of selected things.");
+ assert.equal($('#test button:not([type="button"])').length, 0);
});
QUnit.test('filtering available options', function(assert) {
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index c5ca0cf7f0..0aeaf3c24d 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -1737,6 +1737,48 @@ class HorizontalVerticalFilterSeleniumTests(AdminWidgetSeleniumTestCase):
self.assertCountSeleniumElements("#id_students_to > option", 2)
+ def test_form_submission_via_enter_key_with_filter_horizontal(self):
+ """
+ The main form can be submitted correctly by pressing the enter key.
+ There is no shadowing from other buttons inside the form.
+ """
+ from selenium.webdriver.common.by import By
+ from selenium.webdriver.common.keys import Keys
+
+ self.school.students.set([self.peter])
+ self.school.alumni.set([self.lisa])
+
+ self.admin_login(username="super", password="secret", login_url="/")
+ self.selenium.get(
+ self.live_server_url
+ + reverse("admin:admin_widgets_school_change", args=(self.school.id,))
+ )
+
+ self.wait_page_ready()
+ self.select_option("#id_students_from", str(self.lisa.id))
+ self.selenium.find_element(By.ID, "id_students_add").click()
+ self.select_option("#id_alumni_from", str(self.peter.id))
+ self.selenium.find_element(By.ID, "id_alumni_add").click()
+
+ # Trigger form submission via Enter key on a text input field.
+ name_input = self.selenium.find_element(By.ID, "id_name")
+ name_input.click()
+ name_input.send_keys(Keys.ENTER)
+
+ # Form was submitted, success message should be shown.
+ self.wait_for_text(
+ "li.success", "The school “School of Awesome” was changed successfully."
+ )
+
+ # Changes should be stored properly in the database.
+ school = School.objects.get(id=self.school.id)
+ self.assertSequenceEqual(
+ school.students.all().order_by("name"), [self.lisa, self.peter]
+ )
+ self.assertSequenceEqual(
+ school.alumni.all().order_by("name"), [self.lisa, self.peter]
+ )
+
class AdminRawIdWidgetSeleniumTests(AdminWidgetSeleniumTestCase):
def setUp(self):