1
0
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:
tanaydin 2024-11-30 17:31:30 +01:00
parent 3d819e2324
commit ac0d653d40
5 changed files with 323 additions and 44 deletions

View File

@ -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);
}
});
}

View File

@ -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 %}

View File

@ -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])

View File

@ -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