Rebuild team/group bulk add as a searchable row formset
The old page gave every eligible member a table row, which a club with a hundred-plus members can't use, and its search was a GET round-trip that discarded anything already ticked. Now you add rows: pick a person from a searchable select, pick a position, fill in the details. The position decides the role. Position.staff_position already distinguishes them, so a staff position creates a StaffAssignment and anything else a TeamMembership -- no separate "player or staff?" control that could disagree with the position picked. Someone joining as both is two rows. Jersey number and captaincy exist only on TeamMembership, so a staff row rejects them and the row script greys them out, keyed off the data-staff marker PositionSelect stamps on staff options. All-or-nothing on submit: one bad row re-renders the page with every row still filled in and the offending field flagged, rather than saving the good rows and losing the rest -- a partial save costs far more when the rows were typed by hand. Cross-row checks no single row can see (the same person twice, two rows claiming one jersey) live on the formset's clean(); per-row checks live on the row form. Eligibility is still never trusted from the POST. Captain and alternate captain on one row is refused as self-contradictory, but how many captains a team may have is deliberately left alone: neither the model nor the single-add form constrains it, and inventing the rule in one entry path only would be bypassable by adding players one at a time. A test pins that absence so it reads as a decision rather than an oversight. Two implementation notes worth keeping: rows are cloned from the template's parsed content, not its innerHTML, because assigning "<tr>...</tr>" to a detached <div> silently drops it; and these tables deliberately skip the usual overflow-x-auto wrapper, which would make a scroll container that clips the picker's dropdown for every row but the first couple. Removing a row leaves TOTAL_FORMS alone -- Django reads a form whose fields are absent from the POST as an unchanged extra and skips it, which is safe, unlike re-indexing live inputs. management/tests.py also carries the setUpTestData rationalisation from the previous commit, which couldn't be split cleanly from the new bulk-add tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
*/
|
||||
(() => {
|
||||
function enhance(select) {
|
||||
// Rows added after page load (see team_bulk_add.html) are enhanced on
|
||||
// demand via window.enhanceSearchableSelect, so a select can be handed
|
||||
// here twice -- without this it would grow a second search input.
|
||||
if (select.dataset.searchableReady === "1") return;
|
||||
select.dataset.searchableReady = "1";
|
||||
|
||||
const isMultiple = select.multiple;
|
||||
const options = Array.from(select.options).filter((option) => option.value !== "");
|
||||
|
||||
@@ -153,5 +159,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Exposed for forms that clone new rows in after this initial sweep has run
|
||||
// (the bulk-add pages) -- the sweep below only ever sees what's already in
|
||||
// the DOM, and a <template>'s inert content is deliberately not matched.
|
||||
window.enhanceSearchableSelect = enhance;
|
||||
|
||||
document.querySelectorAll("select[data-searchable]").forEach(enhance);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user