Skip to content

Closes #18645: Add support for bulk importing multi-termination cables#22641

Merged
arthanson merged 1 commit into
featurefrom
18645-bulk-import-of-multi-termination-cables
Jul 10, 2026
Merged

Closes #18645: Add support for bulk importing multi-termination cables#22641
arthanson merged 1 commit into
featurefrom
18645-bulk-import-of-multi-termination-cables

Conversation

@pheus

@pheus pheus commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes: #18645

This adds support for importing cables with multiple terminations on either side via CSV, JSON, and YAML bulk import.

Cable import now accepts comma-separated values for device, power panel, and termination name fields. Each side can specify either one parent for all terminations or one parent per termination name. Submitted order is preserved so breakout cable profiles can assign connector positions correctly.

This also updates the relevant cable parent fields from CSVModelChoiceField to CSVModelMultipleChoiceField, refreshes their help text, and adds validation for duplicate terminations, empty termination names, parent/name count mismatches, already-connected terminations, and ambiguous parent lookups.

@pheus pheus linked an issue Jul 8, 2026 that may be closed by this pull request
@pheus

pheus commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@claude /review Only report on issues of potential concern; we don't need commentary on anything that doesn't require further consideration.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @pheus's task in 4m 35s —— View job


PR Review: #22641 — Bulk import multi-termination cables

  • Read diff and changed files
  • Identify issues of concern
  • Post findings

The implementation is well-structured with solid test coverage. A few concerns worth considering:


1. mark_safe with translated string concatenation (XSS risk)

bulk_import.py lines 1448–1516 use the pattern:

help_text=mark_safe(
    _('Device name(s) for device component terminations. ...') + ' <code>"device1,device2"</code>'
)

This concatenates a lazy-translated string with a raw HTML literal and then marks the whole thing safe. If a translator supplies a string containing <script> or other HTML, it will be rendered unescaped. The correct approach is format_html, which escapes the translated part:

help_text=format_html(
    '{} <code>"device1,device2"</code>',
    _('Device name(s) for device component terminations. ...')
)

This pattern is pre-existing in the file (e.g., the tagged_vlans field uses it at ~line 1034), but this PR adds five new occurrences. Fix this →


2. _resolve_side_parent_objects re-queries objects Django already validated

_resolve_side_parent_objects (lines 1610–1638) does one field.queryset.get() per parent value to preserve submission order. At this point, CSVModelMultipleChoiceField.clean() has already verified every value exists via Django's ModelMultipleChoiceField._check_values(). This means every cable row with N parent values pays N extra DB round-trips purely for ordering.

A lighter approach would be to look up the objects once and build an ordered list from the cleaned_data queryset — but since Django's ModelMultipleChoiceField returns an unordered queryset, the re-query is the pragmatic solution. Worth noting as a trade-off; for typical import volumes (cables with 2–4 terminations) the overhead is negligible.


3. CSVModelMultipleChoiceField.clean() does not strip whitespace

CSVModelMultipleChoiceField.clean() at utilities/forms/fields/csv.py:101–104:

def clean(self, value):
    if not isinstance(value, list):
        value = value.split(',') if value else []
    return super().clean(value)

This splits on commas without stripping. If a user submits "Device A, Device B" (space after comma), Django's _check_values looks up name=' Device B' (with leading space), which will fail with a confusing "Object not found" error. _split_side_values does strip, but it only runs after Django's field validation already fails — so the user never sees the benefit.

Since this PR introduces the multi-value pattern for cable parents, adding .strip() in CSVModelMultipleChoiceField.clean() would improve the UX broadly (it would affect all existing multi-value fields like tagged_vlans too). Not a blocker, but worth addressing alongside this change. Fix this →


4. Missing test: multi-parent + site-filtered queryset

There is no test covering the combination of side_a_site/side_b_site with multiple parent values. When a site is provided, __init__ filters the device/power_panel queryset, and _resolve_side_parent_objects queries against that filtered queryset. A test verifying that a device outside the specified site is rejected (even if the name is valid) would close this gap.


Enable comma-separated Device, Power Panel, and Termination name lists
in Cable CSV/JSON/YAML imports. Each side accepts either one parent for
all terminations or one parent per name, preserving submission order for
connector assignment.

Add validation for duplicate terminations, empty names, parent count
mismatches, and MultipleObjectsReturned cases. Change side_a/b_device
and side_a/b_power_panel fields from CSVModelChoiceField to
CSVModelMultipleChoiceField with updated help text.

Fixes #18645
@pheus pheus force-pushed the 18645-bulk-import-of-multi-termination-cables branch from 5adcf52 to 87a822d Compare July 10, 2026 11:53
@pheus

pheus commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@claude /review

I agree with the mark_safe() point and I've updated the code.

For _resolve_side_parent_objects(), I’d like to keep the current explicit lookup. The extra queries are intentional here: the cleaned CSVModelMultipleChoiceField value is a queryset, does not preserve submitted order, deduplicates repeated values, and does not give us the same clear MultipleObjectsReturned handling for non-unique to_field_name values. Since cable sides usually have only a small number of terminations, the trade-off seems acceptable.

The whitespace behavior in CSVModelMultipleChoiceField is a valid broader UX issue, but I’d prefer not to change that shared utility field as part of this PR.

I’ll also add a test covering multi-parent import with side_x_site queryset filtering, to make sure an otherwise valid parent name outside the selected site is rejected on the parent field.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @pheus's task in 2m 7s —— View job


Follow-up Review: #22641 — Bulk import multi-termination cables

  • Read trigger comment and understand decisions
  • Check current state of mark_safe fix
  • Check if site-filtering test has been added
  • Review updated code
  • Post findings

mark_safeformat_html (fixed)

The fix is correct. One cosmetic note: format_html will HTML-entity-encode the " characters in the example strings (e.g., '"device1,device2"'&quot;device1,device2&quot; in the rendered markup). This is valid HTML — browsers render it as "device1,device2" — so it's not a bug, just something to be aware of if you ever inspect the DOM. No action needed.

_resolve_side_parent_objects (keeping explicit lookups)

The reasoning is sound and the docstring captures it well. No concerns.

CSVModelMultipleChoiceField whitespace (deferred)

Noted. Worth observing that _split_side_values already strips whitespace, so parent lookups inside _resolve_side_parent_objects are resilient even without the field-level fix. The gap is only that CSVModelMultipleChoiceField.clean() still runs first and would reject "Device A, Device B" before _resolve_side_parent_objects ever sees it — but that's the pre-existing behavior and deferring is fine.

Site-filtering test (added)

test_import_multiple_terminations_site_filtered_parent_queryset covers the scenario exactly: Device X exists at Site 2, side_b_site is set to Site 1, and the test confirms Device X is rejected on side_b_device with "Object not found". Clean.


No remaining concerns. The code looks good to merge.

@pheus pheus marked this pull request as ready for review July 10, 2026 12:08
@pheus pheus requested review from a team and arthanson and removed request for a team July 10, 2026 12:08
@arthanson arthanson merged commit 8e525c8 into feature Jul 10, 2026
12 checks passed
@arthanson arthanson deleted the 18645-bulk-import-of-multi-termination-cables branch July 10, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bulk import of multi-termination cables

2 participants