summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlton Gibson <carlton.gibson@noumenal.es>2021-04-15 17:15:28 +0200
committerCarlton Gibson <carlton.gibson@noumenal.es>2021-04-21 08:31:06 +0200
commit5c73fbb6a93ee214678f02ba4027f18dff49337b (patch)
tree9eba97cddf7490fde7068f4245fcbc33eac49265
parent54e94640ace261b14cf8cdb1fae3dc6f068a5f87 (diff)
Fixed #32647 -- Restored multi-row select with shift-modifier in admin changelist.
Regression in 30e59705fc3e3e9e8370b965af794ad6173bf92b.
-rw-r--r--django/contrib/admin/static/admin/js/actions.js28
-rw-r--r--docs/releases/3.2.1.txt3
-rw-r--r--tests/admin_changelist/tests.py22
3 files changed, 52 insertions, 1 deletions
diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js
index 239aa5dc39..5aee34ea5e 100644
--- a/django/contrib/admin/static/admin/js/actions.js
+++ b/django/contrib/admin/static/admin/js/actions.js
@@ -88,6 +88,16 @@
window.Actions = function(actionCheckboxes, options) {
options = Object.assign({}, defaults, options);
let list_editable_changed = false;
+ let lastChecked = null;
+ let shiftPressed = false;
+
+ document.addEventListener('keydown', (event) => {
+ shiftPressed = event.shiftKey;
+ });
+
+ document.addEventListener('keyup', (event) => {
+ shiftPressed = event.shiftKey;
+ });
document.getElementById(options.allToggleId).addEventListener('click', function(event) {
checker(actionCheckboxes, options, this.checked);
@@ -113,12 +123,28 @@
});
});
+ function affectedCheckboxes(target, withModifier) {
+ const multiSelect = (lastChecked && withModifier && lastChecked !== target);
+ if (!multiSelect) {
+ return [target];
+ }
+ const checkboxes = Array.from(actionCheckboxes);
+ const targetIndex = checkboxes.findIndex(el => el === target);
+ const lastCheckedIndex = checkboxes.findIndex(el => el === lastChecked);
+ const startIndex = Math.min(targetIndex, lastCheckedIndex);
+ const endIndex = Math.max(targetIndex, lastCheckedIndex);
+ const filtered = checkboxes.filter((el, index) => (startIndex <= index) && (index <= endIndex));
+ return filtered;
+ };
+
Array.from(document.getElementById('result_list').tBodies).forEach(function(el) {
el.addEventListener('change', function(event) {
const target = event.target;
if (target.classList.contains('action-select')) {
- target.closest('tr').classList.toggle(options.selectedClass, target.checked);
+ const checkboxes = affectedCheckboxes(target, shiftPressed);
+ checker(checkboxes, options, target.checked);
updateCounter(actionCheckboxes, options);
+ lastChecked = target;
} else {
list_editable_changed = true;
}
diff --git a/docs/releases/3.2.1.txt b/docs/releases/3.2.1.txt
index 6b0b1576cf..76291f57c1 100644
--- a/docs/releases/3.2.1.txt
+++ b/docs/releases/3.2.1.txt
@@ -44,3 +44,6 @@ Bugfixes
* Fixed a regression in Django 3.2 that caused a crash when decoding a cookie
value, used by ``django.contrib.messages.storage.cookie.CookieStorage``, in
the pre-Django 3.2 format (:ticket:`32643`).
+
+* Fixed a regression in Django 3.2 that stopped the shift-key modifier
+ selecting multiple rows in the admin changelist (:ticket:`32647`).
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 28acc401c1..8da1c2f799 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -1381,6 +1381,28 @@ class SeleniumTests(AdminSeleniumTestCase):
self.assertIs(all_selector.get_property('checked'), False)
self.assertEqual(row.get_attribute('class'), '')
+ def test_modifier_allows_multiple_section(self):
+ """
+ Selecting a row and then selecting another row whilst holding shift
+ should select all rows in-between.
+ """
+ from selenium.webdriver.common.action_chains import ActionChains
+ from selenium.webdriver.common.keys import Keys
+
+ Parent.objects.bulk_create([Parent(name='parent%d' % i) for i in range(5)])
+ self.admin_login(username='super', password='secret')
+ self.selenium.get(self.live_server_url + reverse('admin:admin_changelist_parent_changelist'))
+ checkboxes = self.selenium.find_elements_by_css_selector('tr input.action-select')
+ self.assertEqual(len(checkboxes), 5)
+ for c in checkboxes:
+ self.assertIs(c.get_property('checked'), False)
+ # Check first row. Hold-shift and check next-to-last row.
+ checkboxes[0].click()
+ ActionChains(self.selenium).key_down(Keys.SHIFT).click(checkboxes[-2]).key_up(Keys.SHIFT).perform()
+ for c in checkboxes[:-2]:
+ self.assertIs(c.get_property('checked'), True)
+ self.assertIs(checkboxes[-1].get_property('checked'), False)
+
def test_select_all_across_pages(self):
Parent.objects.bulk_create([Parent(name='parent%d' % i) for i in range(101)])
self.admin_login(username='super', password='secret')