mirror of
https://github.com/django/django.git
synced 2025-01-08 17:37:20 +00:00
My all fixes on Django.
This commit is contained in:
parent
3d819e2324
commit
ac0d653d40
@ -51,29 +51,33 @@
|
||||
}
|
||||
actionCheckboxes.forEach(function(el) {
|
||||
el.checked = checked;
|
||||
el.closest('tr').classList.toggle(options.selectedClass, checked);
|
||||
if (!options.inline) {
|
||||
el.closest('tr').classList.toggle(options.selectedClass, checked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateCounter(actionCheckboxes, options) {
|
||||
const sel = Array.from(actionCheckboxes).filter(function(el) {
|
||||
return el.checked;
|
||||
}).length;
|
||||
const counter = document.querySelector(options.counterContainer);
|
||||
// data-actions-icnt is defined in the generated HTML
|
||||
// and contains the total amount of objects in the queryset
|
||||
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
||||
counter.textContent = interpolate(
|
||||
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
|
||||
sel: sel,
|
||||
cnt: actions_icnt
|
||||
}, true);
|
||||
const allToggle = document.getElementById(options.allToggleId);
|
||||
allToggle.checked = sel === actionCheckboxes.length;
|
||||
if (allToggle.checked) {
|
||||
showQuestion(options);
|
||||
} else {
|
||||
clearAcross(options);
|
||||
if (!options.inline) {
|
||||
const sel = Array.from(actionCheckboxes).filter(function(el) {
|
||||
return el.checked;
|
||||
}).length;
|
||||
const counter = document.querySelector(options.counterContainer);
|
||||
// data-actions-icnt is defined in the generated HTML
|
||||
// and contains the total amount of objects in the queryset
|
||||
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
||||
counter.textContent = interpolate(
|
||||
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
|
||||
sel: sel,
|
||||
cnt: actions_icnt
|
||||
}, true);
|
||||
const allToggle = document.getElementById(options.allToggleId);
|
||||
allToggle.checked = sel === actionCheckboxes.length;
|
||||
if (allToggle.checked) {
|
||||
showQuestion(options);
|
||||
} else {
|
||||
clearAcross(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +89,8 @@
|
||||
acrossQuestions: "div.actions span.question",
|
||||
acrossClears: "div.actions span.clear",
|
||||
allToggleId: "action-toggle",
|
||||
selectedClass: "selected"
|
||||
selectedClass: "selected",
|
||||
inline: false,
|
||||
};
|
||||
|
||||
window.Actions = function(actionCheckboxes, options) {
|
||||
@ -102,11 +107,6 @@
|
||||
shiftPressed = event.shiftKey;
|
||||
});
|
||||
|
||||
document.getElementById(options.allToggleId).addEventListener('click', function(event) {
|
||||
checker(actionCheckboxes, options, this.checked);
|
||||
updateCounter(actionCheckboxes, options);
|
||||
});
|
||||
|
||||
document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) {
|
||||
el.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
@ -142,28 +142,70 @@
|
||||
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')) {
|
||||
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
||||
checker(checkboxes, options, target.checked);
|
||||
if (!options.inline) {
|
||||
document
|
||||
.getElementById(options.allToggleId)
|
||||
.addEventListener('click', function(event) {
|
||||
checker(actionCheckboxes, options, this.checked);
|
||||
updateCounter(actionCheckboxes, options);
|
||||
lastChecked = target;
|
||||
} else {
|
||||
list_editable_changed = true;
|
||||
});
|
||||
|
||||
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')) {
|
||||
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
||||
checker(checkboxes, options, target.checked);
|
||||
updateCounter(actionCheckboxes, options);
|
||||
lastChecked = target;
|
||||
} else {
|
||||
list_editable_changed = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#changelist-form button[name=index]').addEventListener('click', function(event) {
|
||||
if (list_editable_changed) {
|
||||
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
|
||||
if (!confirmed) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector('#changelist-form button[name=index]').addEventListener('click', function(event) {
|
||||
if (list_editable_changed) {
|
||||
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
|
||||
if (!confirmed) {
|
||||
event.preventDefault();
|
||||
|
||||
if (options.inline) {
|
||||
const handleCheckboxChange = (event) => {
|
||||
const target = event.target;
|
||||
|
||||
if (!lastChecked || !target.name.endsWith('-DELETE')) {
|
||||
lastChecked = target;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (lastChecked.name.slice(0, -9) === target.name.slice(0, -9)) {
|
||||
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
||||
checker(checkboxes, options, target.checked);
|
||||
}
|
||||
|
||||
lastChecked = target;
|
||||
};
|
||||
|
||||
const attachChangeListener = (element) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.addEventListener('change', handleCheckboxChange);
|
||||
};
|
||||
|
||||
// Handle tabular inline tables
|
||||
document.querySelectorAll('.tabular_inline_table tbody').forEach(attachChangeListener);
|
||||
|
||||
// Handle stacked inlines
|
||||
document.querySelectorAll('.inline-related').forEach(attachChangeListener);
|
||||
}
|
||||
|
||||
const el = document.querySelector('#changelist-form input[name=_save]');
|
||||
// The button does not exist if no fields are editable.
|
||||
@ -200,5 +242,16 @@
|
||||
if (actionsEls.length > 0) {
|
||||
Actions(actionsEls);
|
||||
}
|
||||
const tabularActionsEls = document.querySelectorAll(
|
||||
'td.delete input[type="checkbox"]');
|
||||
if (tabularActionsEls.length > 0) {
|
||||
defaults.inline = true;
|
||||
Actions(tabularActionsEls);
|
||||
}
|
||||
const stackedActionsEls = document.querySelectorAll('span.delete input[type="checkbox"]');
|
||||
if (stackedActionsEls.length > 0) {
|
||||
defaults.inline = true;
|
||||
Actions(stackedActionsEls);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
</h2>
|
||||
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
|
||||
{{ inline_admin_formset.formset.non_form_errors }}
|
||||
<table>
|
||||
<table class="tabular_inline_table">
|
||||
<thead><tr>
|
||||
<th class="original"></th>
|
||||
{% for field in inline_admin_formset.fields %}
|
||||
|
Binary file not shown.
@ -320,10 +320,18 @@ class ConsigliereInline(admin.TabularInline):
|
||||
model = Consigliere
|
||||
|
||||
|
||||
class ConsigliereInlineStacked(admin.StackedInline):
|
||||
model = Consigliere
|
||||
|
||||
|
||||
class SottoCapoInline(admin.TabularInline):
|
||||
model = SottoCapo
|
||||
|
||||
|
||||
class SottoCapoInlineStacked(admin.StackedInline):
|
||||
model = SottoCapo
|
||||
|
||||
|
||||
class ProfileInline(admin.TabularInline):
|
||||
model = Profile
|
||||
extra = 1
|
||||
@ -510,7 +518,14 @@ site.register(Holder4, Holder4Admin)
|
||||
site.register(Holder5, Holder5Admin)
|
||||
site.register(Author, AuthorAdmin)
|
||||
site.register(
|
||||
CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]
|
||||
CapoFamiglia,
|
||||
inlines=[
|
||||
ConsigliereInline,
|
||||
SottoCapoInline,
|
||||
ReadOnlyInlineInline,
|
||||
ConsigliereInlineStacked,
|
||||
SottoCapoInlineStacked,
|
||||
],
|
||||
)
|
||||
site.register(ProfileCollection, inlines=[ProfileInline])
|
||||
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
|
||||
|
@ -1931,6 +1931,217 @@ class SeleniumTests(AdminSeleniumTestCase):
|
||||
with self.disable_implicit_wait():
|
||||
self.assertCountSeleniumElements(rows_selector, 0)
|
||||
|
||||
def test_delete_multiselect_tabular_inline(self):
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
def fill_form_data():
|
||||
self.selenium.find_element(By.ID, "id_name").send_keys("Family Name")
|
||||
# Fill inline forms
|
||||
for group in [1, 2]:
|
||||
for i in range(3):
|
||||
self.selenium.find_element(
|
||||
By.ID, f"id_{-group}-{i}-name"
|
||||
).send_keys(f"person name {group} {i+1}")
|
||||
|
||||
def get_delete_checkboxes():
|
||||
checkboxes = {}
|
||||
for group in [1, 2, 3, 4]:
|
||||
for i in range(3):
|
||||
key = f"{group}_{i}"
|
||||
checkboxes[key] = self.selenium.find_element(
|
||||
By.ID, f"id_-{group}-{i}-DELETE"
|
||||
)
|
||||
return checkboxes
|
||||
|
||||
def scroll_to_element(element):
|
||||
self.selenium.execute_script(
|
||||
"window.scrollTo(0, %s);" % element.location["y"]
|
||||
)
|
||||
|
||||
def shift_click_range(start_element, end_element):
|
||||
actions = ActionChains(self.selenium)
|
||||
actions.move_to_element(start_element)
|
||||
actions.key_down(Keys.SHIFT)
|
||||
actions.click()
|
||||
actions.move_to_element(end_element)
|
||||
actions.click()
|
||||
actions.key_up(Keys.SHIFT)
|
||||
actions.perform()
|
||||
|
||||
def assert_checkbox_states(checkboxes, expected_states):
|
||||
for key, expected in expected_states.items():
|
||||
actual = checkboxes[key].get_property("checked")
|
||||
self.assertEqual(
|
||||
actual,
|
||||
expected,
|
||||
f"Checkbox {key} state mismatch."
|
||||
f" Expected: {expected}, Got: {actual}",
|
||||
)
|
||||
|
||||
# Setup
|
||||
self.admin_login(username="super", password="secret")
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("admin:admin_inlines_capofamiglia_add")
|
||||
)
|
||||
|
||||
# Fill and save form
|
||||
fill_form_data()
|
||||
with self.wait_page_loaded():
|
||||
btn = self.selenium.find_element(
|
||||
By.XPATH, '//input[@value="Save and continue editing"]'
|
||||
)
|
||||
scroll_to_element(btn)
|
||||
btn.click()
|
||||
|
||||
# Get all delete checkboxes
|
||||
checkboxes = get_delete_checkboxes()
|
||||
|
||||
# Test 1: Initial state - all unchecked
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"1_0": False,
|
||||
"1_1": False,
|
||||
"1_2": False,
|
||||
"2_0": False,
|
||||
"2_1": False,
|
||||
"2_2": False,
|
||||
"3_0": False,
|
||||
"3_1": False,
|
||||
"3_2": False,
|
||||
"4_0": False,
|
||||
"4_1": False,
|
||||
"4_2": False,
|
||||
},
|
||||
)
|
||||
|
||||
# Test 2: Select first row completely
|
||||
scroll_to_element(checkboxes["1_0"])
|
||||
shift_click_range(checkboxes["1_0"], checkboxes["1_2"])
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"1_0": True,
|
||||
"1_1": True,
|
||||
"1_2": True,
|
||||
"2_0": False,
|
||||
"2_1": False,
|
||||
"2_2": False,
|
||||
},
|
||||
)
|
||||
|
||||
# Test 3: Unselect middle checkbox in first row
|
||||
scroll_to_element(checkboxes["1_0"])
|
||||
shift_click_range(checkboxes["1_2"], checkboxes["1_0"])
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"1_0": False,
|
||||
"1_1": False,
|
||||
"1_2": False,
|
||||
"2_0": False,
|
||||
"2_1": False,
|
||||
"2_2": False,
|
||||
},
|
||||
)
|
||||
|
||||
# Test 4: Select across rows
|
||||
actions = ActionChains(self.selenium)
|
||||
scroll_to_element(checkboxes["1_0"])
|
||||
actions.move_to_element(checkboxes["1_0"]).key_down(
|
||||
Keys.SHIFT
|
||||
).click().perform()
|
||||
scroll_to_element(checkboxes["2_2"])
|
||||
actions.move_to_element(checkboxes["2_2"]).click().perform()
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"1_0": True,
|
||||
"1_1": False,
|
||||
"1_2": False,
|
||||
"2_0": False,
|
||||
"2_1": False,
|
||||
"2_2": True,
|
||||
},
|
||||
)
|
||||
|
||||
# Test 5: Select all remaining checkboxes
|
||||
scroll_to_element(checkboxes["1_0"])
|
||||
shift_click_range(checkboxes["1_0"], checkboxes["1_2"])
|
||||
scroll_to_element(checkboxes["2_0"])
|
||||
shift_click_range(checkboxes["2_0"], checkboxes["2_1"])
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"1_0": True,
|
||||
"1_1": True,
|
||||
"1_2": True,
|
||||
"2_0": True,
|
||||
"2_1": True,
|
||||
"2_2": True,
|
||||
},
|
||||
)
|
||||
|
||||
scroll_to_element(checkboxes["3_0"])
|
||||
shift_click_range(checkboxes["3_0"], checkboxes["3_2"])
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"3_0": True,
|
||||
"3_1": True,
|
||||
"3_2": True,
|
||||
"4_0": False,
|
||||
"4_1": False,
|
||||
"4_2": False,
|
||||
},
|
||||
)
|
||||
scroll_to_element(checkboxes["3_0"])
|
||||
shift_click_range(checkboxes["3_2"], checkboxes["3_0"])
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"3_0": False,
|
||||
"3_1": False,
|
||||
"3_2": False,
|
||||
"4_0": False,
|
||||
"4_1": False,
|
||||
"4_2": False,
|
||||
},
|
||||
)
|
||||
|
||||
actions = ActionChains(self.selenium)
|
||||
scroll_to_element(checkboxes["3_0"])
|
||||
actions.move_to_element(checkboxes["3_0"]).key_down(
|
||||
Keys.SHIFT
|
||||
).click().perform()
|
||||
scroll_to_element(checkboxes["4_2"])
|
||||
actions.move_to_element(checkboxes["4_2"]).click().perform()
|
||||
assert_checkbox_states(
|
||||
checkboxes,
|
||||
{
|
||||
"3_0": True,
|
||||
"3_1": False,
|
||||
"3_2": False,
|
||||
"4_0": False,
|
||||
"4_1": False,
|
||||
"4_2": True,
|
||||
},
|
||||
)
|
||||
|
||||
scroll_to_element(checkboxes["1_0"])
|
||||
shift_click_range(checkboxes["1_0"], checkboxes["1_2"])
|
||||
scroll_to_element(checkboxes["2_0"])
|
||||
shift_click_range(checkboxes["2_0"], checkboxes["2_2"])
|
||||
scroll_to_element(checkboxes["3_0"])
|
||||
shift_click_range(checkboxes["3_0"], checkboxes["3_2"])
|
||||
scroll_to_element(checkboxes["4_0"])
|
||||
shift_click_range(checkboxes["4_0"], checkboxes["4_2"])
|
||||
btn2 = self.selenium.find_element(By.XPATH, '//input[@value="Save"]')
|
||||
scroll_to_element(btn2)
|
||||
btn2.click()
|
||||
|
||||
def test_delete_invalid_stacked_inlines(self):
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.by import By
|
||||
|
Loading…
Reference in New Issue
Block a user