From 8f7f258612f6a7e0fa8649fe58b156361cea0df0 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 21 Jul 2025 21:46:37 +0200 Subject: [PATCH] feat(dcim): Add site fields to Cable bulk import form Introduces `side_a_site` and `side_b_site` fields for the Cable bulk import form. Limits device choices on both sides to the selected site for improved input validation and consistency. --- netbox/dcim/forms/bulk_import.py | 37 ++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 1ea789068..2d0d8490e 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1335,6 +1335,13 @@ class MACAddressImportForm(NetBoxModelImportForm): class CableImportForm(NetBoxModelImportForm): # Termination A + side_a_site = CSVModelChoiceField( + label=_('Side A site'), + queryset=Site.objects.all(), + required=False, + to_field_name='name', + help_text=_('Site of parent device A (if any)'), + ) side_a_device = CSVModelChoiceField( label=_('Side A device'), queryset=Device.objects.all(), @@ -1353,6 +1360,13 @@ class CableImportForm(NetBoxModelImportForm): ) # Termination B + side_b_site = CSVModelChoiceField( + label=_('Side B site'), + queryset=Site.objects.all(), + required=False, + to_field_name='name', + help_text=_('Site of parent device B (if any)'), + ) side_b_device = CSVModelChoiceField( label=_('Side B device'), queryset=Device.objects.all(), @@ -1400,10 +1414,29 @@ class CableImportForm(NetBoxModelImportForm): class Meta: model = Cable fields = [ - 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type', - 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags', + 'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type', + 'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', + 'comments', 'tags', ] + def __init__(self, data=None, *args, **kwargs): + super().__init__(data, *args, **kwargs) + + if data: + # Limit choices for side_a_device to the assigned side_a_site + if side_a_site := data.get('side_a_site'): + side_a_device_params = {f'site__{self.fields["side_a_site"].to_field_name}': side_a_site} + self.fields['side_a_device'].queryset = self.fields['side_a_device'].queryset.filter( + **side_a_device_params + ) + + # Limit choices for side_b_device to the assigned side_b_site + if side_b_site := data.get('side_b_site'): + side_b_device_params = {f'site__{self.fields["side_b_site"].to_field_name}': side_b_site} + self.fields['side_b_device'].queryset = self.fields['side_b_device'].queryset.filter( + **side_b_device_params + ) + def _clean_side(self, side): """ Derive a Cable's A/B termination objects.