mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
* 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. * feat(dcim): Enhance test data setup with multiple sites Refactors tests to create multiple sites and assign devices accordingly. Updates CSV data to include `side_a_site` and `side_b_site` fields for scenarios involving multiple sites. This improves test coverage and alignment with real-world use cases. * docs(dcim): Update comments explaining indent for CSV import Improved the inline comments to clarify the rationale behind allowing devices with duplicate names on different sites during CSV bulk import.
This commit is contained in:
parent
26bec1275f
commit
14c4aeca54
@ -1335,6 +1335,13 @@ class MACAddressImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class CableImportForm(NetBoxModelImportForm):
|
class CableImportForm(NetBoxModelImportForm):
|
||||||
# Termination A
|
# 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(
|
side_a_device = CSVModelChoiceField(
|
||||||
label=_('Side A device'),
|
label=_('Side A device'),
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
@ -1353,6 +1360,13 @@ class CableImportForm(NetBoxModelImportForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Termination B
|
# 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(
|
side_b_device = CSVModelChoiceField(
|
||||||
label=_('Side B device'),
|
label=_('Side B device'),
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
@ -1400,10 +1414,29 @@ class CableImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = [
|
fields = [
|
||||||
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
|
||||||
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
|
'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):
|
def _clean_side(self, side):
|
||||||
"""
|
"""
|
||||||
Derive a Cable's A/B termination objects.
|
Derive a Cable's A/B termination objects.
|
||||||
|
@ -3266,17 +3266,27 @@ class CableTestCase(
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
sites = (
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
|
devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
|
||||||
role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
||||||
vc = VirtualChassis.objects.create(name='Virtual Chassis')
|
vc = VirtualChassis.objects.create(name='Virtual Chassis')
|
||||||
|
|
||||||
|
# NOTE: By design, NetBox now allows for the creation of devices with the same name if they belong to
|
||||||
|
# different sites.
|
||||||
|
# The CSV test below demonstrates that devices with identical names on different sites can be created
|
||||||
|
# and referenced successfully.
|
||||||
devices = (
|
devices = (
|
||||||
Device(name='Device 1', site=site, device_type=devicetype, role=role),
|
# Create 'Device 1' assigned to 'Site 1'
|
||||||
Device(name='Device 2', site=site, device_type=devicetype, role=role),
|
Device(name='Device 1', site=sites[0], device_type=devicetype, role=role),
|
||||||
Device(name='Device 3', site=site, device_type=devicetype, role=role),
|
Device(name='Device 2', site=sites[0], device_type=devicetype, role=role),
|
||||||
Device(name='Device 4', site=site, device_type=devicetype, role=role),
|
Device(name='Device 3', site=sites[0], device_type=devicetype, role=role),
|
||||||
|
# Create 'Device 1' assigned to 'Site 2' (allowed since the site is different)
|
||||||
|
Device(name='Device 1', site=sites[1], device_type=devicetype, role=role),
|
||||||
)
|
)
|
||||||
Device.objects.bulk_create(devices)
|
Device.objects.bulk_create(devices)
|
||||||
|
|
||||||
@ -3327,13 +3337,15 @@ class CableTestCase(
|
|||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure that CSV bulk import supports assigning terminations from parent devices that share
|
||||||
|
# the same device name, provided those devices belong to different sites.
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
|
"side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
|
||||||
"Device 3,dcim.interface,Interface 1,Device 4,dcim.interface,Interface 1",
|
"Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
|
||||||
"Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2",
|
"Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
|
||||||
"Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3",
|
"Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
|
||||||
"Device 1,dcim.interface,Device 2 Interface,Device 4,dcim.interface,Interface 4",
|
"Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
|
||||||
"Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5",
|
"Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
Loading…
Reference in New Issue
Block a user