Derive attribute labels from name if not passed for instance

This commit is contained in:
Jeremy Stretch 2025-11-05 10:51:18 -05:00
parent 1de41b4964
commit 838794a5cf
4 changed files with 79 additions and 73 deletions

View File

@ -4,61 +4,61 @@ from netbox.ui import attrs, panels
class SitePanel(panels.ObjectPanel): class SitePanel(panels.ObjectPanel):
region = attrs.NestedObjectAttr('region', label=_('Region'), linkify=True) region = attrs.NestedObjectAttr('region', linkify=True)
group = attrs.NestedObjectAttr('group', label=_('Group'), linkify=True) group = attrs.NestedObjectAttr('group', linkify=True)
status = attrs.ChoiceAttr('status', label=_('Status')) status = attrs.ChoiceAttr('status')
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
facility = attrs.TextAttr('facility', label=_('Facility')) facility = attrs.TextAttr('facility')
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')
timezone = attrs.TimezoneAttr('time_zone', label=_('Timezone')) timezone = attrs.TimezoneAttr('time_zone')
physical_address = attrs.AddressAttr('physical_address', label=_('Physical address'), map_url=True) physical_address = attrs.AddressAttr('physical_address', map_url=True)
shipping_address = attrs.AddressAttr('shipping_address', label=_('Shipping address'), map_url=True) shipping_address = attrs.AddressAttr('shipping_address', map_url=True)
gps_coordinates = attrs.GPSCoordinatesAttr() gps_coordinates = attrs.GPSCoordinatesAttr()
class LocationPanel(panels.NestedGroupObjectPanel): class LocationPanel(panels.NestedGroupObjectPanel):
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group') site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
status = attrs.ChoiceAttr('status', label=_('Status')) status = attrs.ChoiceAttr('status')
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
facility = attrs.TextAttr('facility', label=_('Facility')) facility = attrs.TextAttr('facility')
class RackDimensionsPanel(panels.ObjectPanel): class RackDimensionsPanel(panels.ObjectPanel):
form_factor = attrs.ChoiceAttr('form_factor', label=_('Form factor')) form_factor = attrs.ChoiceAttr('form_factor')
width = attrs.ChoiceAttr('width', label=_('Width')) width = attrs.ChoiceAttr('width')
u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height')) u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display', label=_('Outer width')) outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display', label=_('Outer height')) outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display', label=_('Outer depth')) outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm', label=_('Mounting depth')) mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm')
class RackNumberingPanel(panels.ObjectPanel): class RackNumberingPanel(panels.ObjectPanel):
starting_unit = attrs.TextAttr('starting_unit', label=_('Starting unit')) starting_unit = attrs.TextAttr('starting_unit')
desc_units = attrs.BooleanAttr('desc_units', label=_('Descending units')) desc_units = attrs.BooleanAttr('desc_units', label=_('Descending units'))
class RackPanel(panels.ObjectPanel): class RackPanel(panels.ObjectPanel):
region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True) region = attrs.NestedObjectAttr('site.region', linkify=True)
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group') site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True) location = attrs.NestedObjectAttr('location', linkify=True)
facility = attrs.TextAttr('facility', label=_('Facility ID')) facility = attrs.TextAttr('facility')
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
status = attrs.ChoiceAttr('status', label=_('Status')) status = attrs.ChoiceAttr('status')
rack_type = attrs.ObjectAttr('rack_type', label=_('Rack type'), linkify=True, grouped_by='manufacturer') rack_type = attrs.ObjectAttr('rack_type', linkify=True, grouped_by='manufacturer')
role = attrs.ObjectAttr('role', label=_('Role'), linkify=True) role = attrs.ObjectAttr('role', linkify=True)
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True) serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
asset_tag = attrs.TextAttr('asset_tag', label=_('Asset tag'), style='font-monospace', copy_button=True) asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow')) airflow = attrs.ChoiceAttr('airflow')
space_utilization = attrs.UtilizationAttr('get_utilization', label=_('Space utilization')) space_utilization = attrs.UtilizationAttr('get_utilization')
power_utilization = attrs.UtilizationAttr('get_power_utilization', label=_('Power utilization')) power_utilization = attrs.UtilizationAttr('get_power_utilization')
class RackWeightPanel(panels.ObjectPanel): class RackWeightPanel(panels.ObjectPanel):
weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display', label=_('Weight')) weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
max_weight = attrs.NumericAttr('max_weight', unit_accessor='get_weight_unit_display', label=_('Maximum weight')) max_weight = attrs.NumericAttr('max_weight', unit_accessor='get_weight_unit_display', label=_('Maximum weight'))
total_weight = attrs.NumericAttr('total_weight', unit_accessor='get_weight_unit_display', label=_('Total weight')) total_weight = attrs.NumericAttr('total_weight', unit_accessor='get_weight_unit_display')
class RackRolePanel(panels.OrganizationalObjectPanel): class RackRolePanel(panels.OrganizationalObjectPanel):
@ -66,37 +66,33 @@ class RackRolePanel(panels.OrganizationalObjectPanel):
class RackTypePanel(panels.ObjectPanel): class RackTypePanel(panels.ObjectPanel):
manufacturer = attrs.ObjectAttr('manufacturer', label=_('Manufacturer'), linkify=True) manufacturer = attrs.ObjectAttr('manufacturer', linkify=True)
model = attrs.TextAttr('model', label=_('Model')) model = attrs.TextAttr('model')
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow')) airflow = attrs.ChoiceAttr('airflow')
class DevicePanel(panels.ObjectPanel): class DevicePanel(panels.ObjectPanel):
region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True) region = attrs.NestedObjectAttr('site.region', linkify=True)
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group') site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True) location = attrs.NestedObjectAttr('location', linkify=True)
rack = attrs.TemplatedAttr('rack', label=_('Rack'), template_name='dcim/device/attrs/rack.html') rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', label=_('Virtual chassis'), linkify=True) virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', linkify=True)
parent_device = attrs.TemplatedAttr( parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html')
'parent_bay',
label=_('Parent device'),
template_name='dcim/device/attrs/parent_device.html',
)
gps_coordinates = attrs.GPSCoordinatesAttr() gps_coordinates = attrs.GPSCoordinatesAttr()
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
device_type = attrs.ObjectAttr('device_type', label=_('Device type'), linkify=True, grouped_by='manufacturer') device_type = attrs.ObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow')) airflow = attrs.ChoiceAttr('airflow')
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True) serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
asset_tag = attrs.TextAttr('asset_tag', label=_('Asset tag'), style='font-monospace', copy_button=True) asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
config_template = attrs.ObjectAttr('config_template', label=_('Config template'), linkify=True) config_template = attrs.ObjectAttr('config_template', linkify=True)
class DeviceManagementPanel(panels.ObjectPanel): class DeviceManagementPanel(panels.ObjectPanel):
status = attrs.ChoiceAttr('status', label=_('Status')) status = attrs.ChoiceAttr('status')
role = attrs.NestedObjectAttr('role', label=_('Role'), linkify=True, max_depth=3) role = attrs.NestedObjectAttr('role', linkify=True, max_depth=3)
platform = attrs.NestedObjectAttr('platform', label=_('Platform'), linkify=True, max_depth=3) platform = attrs.NestedObjectAttr('platform', linkify=True, max_depth=3)
primary_ip4 = attrs.TemplatedAttr( primary_ip4 = attrs.TemplatedAttr(
'primary_ip4', 'primary_ip4',
label=_('Primary IPv4'), label=_('Primary IPv4'),
@ -115,21 +111,21 @@ class DeviceManagementPanel(panels.ObjectPanel):
class DeviceTypePanel(panels.ObjectPanel): class DeviceTypePanel(panels.ObjectPanel):
manufacturer = attrs.ObjectAttr('manufacturer', label=_('Manufacturer'), linkify=True) manufacturer = attrs.ObjectAttr('manufacturer', linkify=True)
model = attrs.TextAttr('model', label=_('Model')) model = attrs.TextAttr('model')
part_number = attrs.TextAttr('part_number', label=_('Part number')) part_number = attrs.TextAttr('part_number')
default_platform = attrs.ObjectAttr('default_platform', label=_('Default platform'), linkify=True) default_platform = attrs.ObjectAttr('default_platform', linkify=True)
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')
u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height')) u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization', label=_('Exclude from utilization')) exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
full_depth = attrs.BooleanAttr('is_full_depth', label=_('Full depth')) full_depth = attrs.BooleanAttr('is_full_depth')
weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display', label=_('Weight')) weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
subdevice_role = attrs.ChoiceAttr('subdevice_role', label=_('Parent/child')) subdevice_role = attrs.ChoiceAttr('subdevice_role', label=_('Parent/child'))
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow')) airflow = attrs.ChoiceAttr('airflow')
front_image = attrs.ImageAttr('front_image', label=_('Front image')) front_image = attrs.ImageAttr('front_image')
rear_image = attrs.ImageAttr('rear_image', label=_('Rear image')) rear_image = attrs.ImageAttr('rear_image')
class ModuleTypeProfilePanel(panels.ObjectPanel): class ModuleTypeProfilePanel(panels.ObjectPanel):
name = attrs.TextAttr('name', label=_('Name')) name = attrs.TextAttr('name')
description = attrs.TextAttr('description', label=_('Description')) description = attrs.TextAttr('description')

View File

@ -10,6 +10,7 @@ from utilities.views import get_viewname
__all__ = ( __all__ = (
'AddObject', 'AddObject',
'CopyContent',
'PanelAction', 'PanelAction',
) )

View File

@ -221,7 +221,7 @@ class AddressAttr(Attr):
class GPSCoordinatesAttr(Attr): class GPSCoordinatesAttr(Attr):
template_name = 'ui/attrs/gps_coordinates.html' template_name = 'ui/attrs/gps_coordinates.html'
label = _('GPS Coordinates') label = _('GPS coordinates')
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs): def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
super().__init__(accessor=None, **kwargs) super().__init__(accessor=None, **kwargs)

View File

@ -132,6 +132,15 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
self.only = only or [] self.only = only or []
self.exclude = exclude or [] self.exclude = exclude or []
@staticmethod
def _name_to_label(name):
"""
Format an attribute's name to be presented as a human-friendly label.
"""
label = name[:1].upper() + name[1:]
label = label.replace('_', ' ')
return label
def get_context(self, context): def get_context(self, context):
""" """
Return the context data to be used when rendering the panel. Return the context data to be used when rendering the panel.
@ -153,7 +162,7 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
'title': self.title or title(obj._meta.verbose_name), 'title': self.title or title(obj._meta.verbose_name),
'attrs': [ 'attrs': [
{ {
'label': attr.label or title(name), 'label': attr.label or self._name_to_label(name),
'value': attr.render(obj, {'name': name}), 'value': attr.render(obj, {'name': name}),
} for name, attr in self._attrs.items() if name in attr_names } for name, attr in self._attrs.items() if name in attr_names
], ],