mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
My all fixes on Django.
This commit is contained in:
parent
3d819e2324
commit
ac0d653d40
@ -51,29 +51,33 @@
|
|||||||
}
|
}
|
||||||
actionCheckboxes.forEach(function(el) {
|
actionCheckboxes.forEach(function(el) {
|
||||||
el.checked = checked;
|
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) {
|
function updateCounter(actionCheckboxes, options) {
|
||||||
const sel = Array.from(actionCheckboxes).filter(function(el) {
|
if (!options.inline) {
|
||||||
return el.checked;
|
const sel = Array.from(actionCheckboxes).filter(function(el) {
|
||||||
}).length;
|
return el.checked;
|
||||||
const counter = document.querySelector(options.counterContainer);
|
}).length;
|
||||||
// data-actions-icnt is defined in the generated HTML
|
const counter = document.querySelector(options.counterContainer);
|
||||||
// and contains the total amount of objects in the queryset
|
// data-actions-icnt is defined in the generated HTML
|
||||||
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
// and contains the total amount of objects in the queryset
|
||||||
counter.textContent = interpolate(
|
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
||||||
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
|
counter.textContent = interpolate(
|
||||||
sel: sel,
|
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
|
||||||
cnt: actions_icnt
|
sel: sel,
|
||||||
}, true);
|
cnt: actions_icnt
|
||||||
const allToggle = document.getElementById(options.allToggleId);
|
}, true);
|
||||||
allToggle.checked = sel === actionCheckboxes.length;
|
const allToggle = document.getElementById(options.allToggleId);
|
||||||
if (allToggle.checked) {
|
allToggle.checked = sel === actionCheckboxes.length;
|
||||||
showQuestion(options);
|
if (allToggle.checked) {
|
||||||
} else {
|
showQuestion(options);
|
||||||
clearAcross(options);
|
} else {
|
||||||
|
clearAcross(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +89,8 @@
|
|||||||
acrossQuestions: "div.actions span.question",
|
acrossQuestions: "div.actions span.question",
|
||||||
acrossClears: "div.actions span.clear",
|
acrossClears: "div.actions span.clear",
|
||||||
allToggleId: "action-toggle",
|
allToggleId: "action-toggle",
|
||||||
selectedClass: "selected"
|
selectedClass: "selected",
|
||||||
|
inline: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.Actions = function(actionCheckboxes, options) {
|
window.Actions = function(actionCheckboxes, options) {
|
||||||
@ -102,11 +107,6 @@
|
|||||||
shiftPressed = event.shiftKey;
|
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) {
|
document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) {
|
||||||
el.addEventListener('click', function(event) {
|
el.addEventListener('click', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -142,28 +142,70 @@
|
|||||||
return filtered;
|
return filtered;
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.from(document.getElementById('result_list').tBodies).forEach(function(el) {
|
if (!options.inline) {
|
||||||
el.addEventListener('change', function(event) {
|
document
|
||||||
const target = event.target;
|
.getElementById(options.allToggleId)
|
||||||
if (target.classList.contains('action-select')) {
|
.addEventListener('click', function(event) {
|
||||||
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
checker(actionCheckboxes, options, this.checked);
|
||||||
checker(checkboxes, options, target.checked);
|
|
||||||
updateCounter(actionCheckboxes, options);
|
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) {
|
if (options.inline) {
|
||||||
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
|
const handleCheckboxChange = (event) => {
|
||||||
if (!confirmed) {
|
const target = event.target;
|
||||||
event.preventDefault();
|
|
||||||
|
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]');
|
const el = document.querySelector('#changelist-form input[name=_save]');
|
||||||
// The button does not exist if no fields are editable.
|
// The button does not exist if no fields are editable.
|
||||||
@ -200,5 +242,16 @@
|
|||||||
if (actionsEls.length > 0) {
|
if (actionsEls.length > 0) {
|
||||||
Actions(actionsEls);
|
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>
|
</h2>
|
||||||
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
|
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
|
||||||
{{ inline_admin_formset.formset.non_form_errors }}
|
{{ inline_admin_formset.formset.non_form_errors }}
|
||||||
<table>
|
<table class="tabular_inline_table">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th class="original"></th>
|
<th class="original"></th>
|
||||||
{% for field in inline_admin_formset.fields %}
|
{% for field in inline_admin_formset.fields %}
|
||||||
|
Binary file not shown.
@ -320,10 +320,18 @@ class ConsigliereInline(admin.TabularInline):
|
|||||||
model = Consigliere
|
model = Consigliere
|
||||||
|
|
||||||
|
|
||||||
|
class ConsigliereInlineStacked(admin.StackedInline):
|
||||||
|
model = Consigliere
|
||||||
|
|
||||||
|
|
||||||
class SottoCapoInline(admin.TabularInline):
|
class SottoCapoInline(admin.TabularInline):
|
||||||
model = SottoCapo
|
model = SottoCapo
|
||||||
|
|
||||||
|
|
||||||
|
class SottoCapoInlineStacked(admin.StackedInline):
|
||||||
|
model = SottoCapo
|
||||||
|
|
||||||
|
|
||||||
class ProfileInline(admin.TabularInline):
|
class ProfileInline(admin.TabularInline):
|
||||||
model = Profile
|
model = Profile
|
||||||
extra = 1
|
extra = 1
|
||||||
@ -510,7 +518,14 @@ site.register(Holder4, Holder4Admin)
|
|||||||
site.register(Holder5, Holder5Admin)
|
site.register(Holder5, Holder5Admin)
|
||||||
site.register(Author, AuthorAdmin)
|
site.register(Author, AuthorAdmin)
|
||||||
site.register(
|
site.register(
|
||||||
CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]
|
CapoFamiglia,
|
||||||
|
inlines=[
|
||||||
|
ConsigliereInline,
|
||||||
|
SottoCapoInline,
|
||||||
|
ReadOnlyInlineInline,
|
||||||
|
ConsigliereInlineStacked,
|
||||||
|
SottoCapoInlineStacked,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
site.register(ProfileCollection, inlines=[ProfileInline])
|
site.register(ProfileCollection, inlines=[ProfileInline])
|
||||||
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
|
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
|
||||||
|
@ -1931,6 +1931,217 @@ class SeleniumTests(AdminSeleniumTestCase):
|
|||||||
with self.disable_implicit_wait():
|
with self.disable_implicit_wait():
|
||||||
self.assertCountSeleniumElements(rows_selector, 0)
|
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):
|
def test_delete_invalid_stacked_inlines(self):
|
||||||
from selenium.common.exceptions import NoSuchElementException
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
Loading…
x
Reference in New Issue
Block a user