From bf836c9bc2f0582db8a737d3b03015529d731356 Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Tue, 25 Feb 2025 16:55:00 +0100 Subject: [PATCH] Fixes 17357: Use virtual chassis name as fallback for device (#18710) --- netbox/dcim/filtersets.py | 1 + netbox/dcim/models/devices.py | 26 +++++++++++++++----------- netbox/dcim/search.py | 1 + netbox/dcim/svg/racks.py | 6 ++---- netbox/dcim/tables/devices.py | 1 + netbox/dcim/tests/test_models.py | 26 ++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 60c3c4d38..fcb3c7e50 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1193,6 +1193,7 @@ class DeviceFilterSet( return queryset return queryset.filter( Q(name__icontains=value) | + Q(virtual_chassis__name__icontains=value) | Q(serial__icontains=value.strip()) | Q(inventoryitems__serial__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) | diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 12b0dae18..a4da28803 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -802,14 +802,10 @@ class Device( verbose_name_plural = _('devices') def __str__(self): - if self.name and self.asset_tag: - return f'{self.name} ({self.asset_tag})' - elif self.name: - return self.name - elif self.virtual_chassis and self.asset_tag: - return f'{self.virtual_chassis.name}:{self.vc_position} ({self.asset_tag})' - elif self.virtual_chassis: - return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' + if self.label and self.asset_tag: + return f'{self.label} ({self.asset_tag})' + elif self.label: + return self.label elif self.device_type and self.asset_tag: return f'{self.device_type.manufacturer} {self.device_type.model} ({self.asset_tag})' elif self.device_type: @@ -1073,14 +1069,22 @@ class Device( device.location = self.location device.save() + @property + def label(self): + """ + Return the device name if set; otherwise return a generated name if available. + """ + if self.name: + return self.name + if self.virtual_chassis: + return f'{self.virtual_chassis.name}:{self.vc_position}' + @property def identifier(self): """ Return the device name if set; otherwise return the Device's primary key as {pk} """ - if self.name is not None: - return self.name - return '{{{}}}'.format(self.pk) + return self.label or '{{{}}}'.format(self.pk) @property def primary_ip(self): diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py index b964421de..964880990 100644 --- a/netbox/dcim/search.py +++ b/netbox/dcim/search.py @@ -44,6 +44,7 @@ class DeviceIndex(SearchIndex): ('asset_tag', 50), ('serial', 60), ('name', 100), + ('virtual_chassis', 200), ('description', 500), ('comments', 5000), ) diff --git a/netbox/dcim/svg/racks.py b/netbox/dcim/svg/racks.py index 94dbeeac2..de695664a 100644 --- a/netbox/dcim/svg/racks.py +++ b/netbox/dcim/svg/racks.py @@ -30,10 +30,8 @@ STROKE_RESERVED = '#4d4dff' def get_device_name(device): - if device.virtual_chassis: - name = f'{device.virtual_chassis.name}:{device.vc_position}' - elif device.name: - name = device.name + if device.label: + name = device.label else: name = str(device.device_type) if device.devicebay_count: diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 924ba0aaf..25875d7bb 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -143,6 +143,7 @@ class PlatformTable(NetBoxTable): class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): name = tables.TemplateColumn( verbose_name=_('Name'), + accessor=Accessor('label'), template_code=DEVICE_LINK, linkify=True ) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index ff1eddd56..c8c84dafb 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -590,6 +590,32 @@ class DeviceTestCase(TestCase): device2.full_clean() device2.save() + def test_device_label(self): + device1 = Device( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + role=DeviceRole.objects.first(), + name=None, + ) + self.assertEqual(device1.label, None) + + device1.name = 'Test Device 1' + self.assertEqual(device1.label, 'Test Device 1') + + virtual_chassis = VirtualChassis.objects.create(name='VC 1') + device2 = Device( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + role=DeviceRole.objects.first(), + name=None, + virtual_chassis=virtual_chassis, + vc_position=2, + ) + self.assertEqual(device2.label, 'VC 1:2') + + device2.name = 'Test Device 2' + self.assertEqual(device2.label, 'Test Device 2') + def test_device_mismatched_site_cluster(self): cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') Cluster.objects.create(name='Cluster 1', type=cluster_type)