Enhance display name of devices

If a name is not defined for a device, it is automatically generated
from other device data (i.e. virtual chassis member) if possible. This
name is available as 'label' attribute to not interfere with existing
functionality or break APIs.
This commit is contained in:
Alexander Haase 2025-02-23 10:26:37 +01:00
parent b02bf77888
commit 2b7c425b57
4 changed files with 46 additions and 15 deletions

View File

@ -802,14 +802,10 @@ class Device(
verbose_name_plural = _('devices') verbose_name_plural = _('devices')
def __str__(self): def __str__(self):
if self.name and self.asset_tag: if self.label and self.asset_tag:
return f'{self.name} ({self.asset_tag})' return f'{self.label} ({self.asset_tag})'
elif self.name: elif self.label:
return self.name return self.label
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})'
elif self.device_type and self.asset_tag: elif self.device_type and self.asset_tag:
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.asset_tag})' return f'{self.device_type.manufacturer} {self.device_type.model} ({self.asset_tag})'
elif self.device_type: elif self.device_type:
@ -1073,14 +1069,22 @@ class Device(
device.location = self.location device.location = self.location
device.save() device.save()
@cached_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 @property
def identifier(self): def identifier(self):
""" """
Return the device name if set; otherwise return the Device's primary key as {pk} Return the device name if set; otherwise return the Device's primary key as {pk}
""" """
if self.name is not None: return self.label or '{{{}}}'.format(self.pk)
return self.name
return '{{{}}}'.format(self.pk)
@property @property
def primary_ip(self): def primary_ip(self):

View File

@ -30,10 +30,8 @@ STROKE_RESERVED = '#4d4dff'
def get_device_name(device): def get_device_name(device):
if device.virtual_chassis: if device.label:
name = f'{device.virtual_chassis.name}:{device.vc_position}' name = device.label
elif device.name:
name = device.name
else: else:
name = str(device.device_type) name = str(device.device_type)
if device.devicebay_count: if device.devicebay_count:

View File

@ -143,6 +143,7 @@ class PlatformTable(NetBoxTable):
class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
name = tables.TemplateColumn( name = tables.TemplateColumn(
verbose_name=_('Name'), verbose_name=_('Name'),
accessor=Accessor('label'),
template_code=DEVICE_LINK, template_code=DEVICE_LINK,
linkify=True linkify=True
) )

View File

@ -590,6 +590,34 @@ class DeviceTestCase(TestCase):
device2.full_clean() device2.full_clean()
device2.save() 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)
device2 = Device(
site=Site.objects.first(),
device_type=DeviceType.objects.first(),
role=DeviceRole.objects.first(),
name='Test Device 2',
)
self.assertEqual(device2.label, 'Test Device 2')
virtual_chassis = VirtualChassis.objects.create(name='VC 1')
device3 = 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(device3.label, 'VC 1:2')
def test_device_mismatched_site_cluster(self): def test_device_mismatched_site_cluster(self):
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
Cluster.objects.create(name='Cluster 1', type=cluster_type) Cluster.objects.create(name='Cluster 1', type=cluster_type)