diff --git a/docs/models/dcim/poweroutlet.md b/docs/models/dcim/poweroutlet.md index 5c8bd6ff06..fe93900561 100644 --- a/docs/models/dcim/poweroutlet.md +++ b/docs/models/dcim/poweroutlet.md @@ -29,6 +29,10 @@ An alternative physical label identifying the power outlet. The type of power outlet. +### Color + +The power outlet's color (optional). + ### Power Port When modeling a device which redistributes power from an upstream supply, such as a power distribution unit (PDU), each power outlet should be mapped to the respective [power port](./powerport.md) on the device which supplies power. For example, a 24-outlet PDU may two power ports, each distributing power to 12 of its outlets. diff --git a/netbox/dcim/api/serializers_/device_components.py b/netbox/dcim/api/serializers_/device_components.py index db029be7b9..e285ce3493 100644 --- a/netbox/dcim/api/serializers_/device_components.py +++ b/netbox/dcim/api/serializers_/device_components.py @@ -155,7 +155,7 @@ class PowerOutletSerializer(NetBoxModelSerializer, CabledObjectSerializer, Conne class Meta: model = PowerOutlet fields = [ - 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'power_port', + 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 8438cd1fa7..04ac3a3d20 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1594,7 +1594,7 @@ class PowerOutletFilterSet( class Meta: model = PowerOutlet fields = ( - 'id', 'name', 'label', 'feed_leg', 'description', 'mark_connected', 'cable_end', + 'id', 'name', 'label', 'feed_leg', 'description', 'color', 'mark_connected', 'cable_end', ) diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index 2939b986e8..0ea3aee634 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -69,7 +69,7 @@ class PowerPortBulkCreateForm( class PowerOutletBulkCreateForm( - form_from_model(PowerOutlet, ['type', 'feed_leg', 'mark_connected']), + form_from_model(PowerOutlet, ['type', 'color', 'feed_leg', 'mark_connected']), DeviceBulkAddComponentForm ): model = PowerOutlet diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 1224f6aea2..01e5f138e7 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1361,7 +1361,7 @@ class PowerPortBulkEditForm( class PowerOutletBulkEditForm( ComponentBulkEditForm, - form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']) + form_from_model(PowerOutlet, ['label', 'type', 'color', 'feed_leg', 'power_port', 'mark_connected', 'description']) ): mark_connected = forms.NullBooleanField( label=_('Mark connected'), @@ -1371,7 +1371,7 @@ class PowerOutletBulkEditForm( model = PowerOutlet fieldsets = ( - FieldSet('module', 'type', 'label', 'description', 'mark_connected'), + FieldSet('module', 'type', 'label', 'description', 'mark_connected', 'color'), FieldSet('feed_leg', 'power_port', name=_('Power')), ) nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description') diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 45579e2c68..1b3cfd2578 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -798,7 +798,7 @@ class PowerOutletImportForm(NetBoxModelImportForm): class Meta: model = PowerOutlet - fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags') + fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index e6ffe0906d..10770824c4 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -1303,7 +1303,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = PowerOutlet fieldsets = ( FieldSet('q', 'filter_id', 'tag'), - FieldSet('name', 'label', 'type', name=_('Attributes')), + FieldSet('name', 'label', 'type', 'color', name=_('Attributes')), FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')), FieldSet( 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', @@ -1317,6 +1317,10 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): required=False ) tag = TagFilterField(model) + color = ColorField( + label=_('Color'), + required=False + ) class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 3bbf1bdce7..095882d13f 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1285,7 +1285,7 @@ class PowerOutletForm(ModularDeviceComponentForm): fieldsets = ( FieldSet( - 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', + 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description', 'tags', ), ) @@ -1293,7 +1293,7 @@ class PowerOutletForm(ModularDeviceComponentForm): class Meta: model = PowerOutlet fields = [ - 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', + 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description', 'tags', ] diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index bce6f06ac2..cd863837a1 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -569,6 +569,7 @@ class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin): ) class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin): power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None + color: str @strawberry_django.type( diff --git a/netbox/dcim/migrations/0191_poweroutlet_color.py b/netbox/dcim/migrations/0191_poweroutlet_color.py new file mode 100644 index 0000000000..6d03eecdca --- /dev/null +++ b/netbox/dcim/migrations/0191_poweroutlet_color.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.9 on 2024-09-26 19:31 + +import utilities.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0190_nested_modules'), + ] + + operations = [ + migrations.AddField( + model_name='poweroutlet', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index f73d040572..9fc3e39517 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -480,6 +480,10 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki blank=True, help_text=_('Phase (for three-phase feeds)') ) + color = ColorField( + verbose_name=_('color'), + blank=True + ) clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg') diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index b92f1cea09..b39a2b87fc 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -512,6 +512,7 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable): verbose_name=_('Power Port'), linkify=True ) + color = columns.ColorColumn() tags = columns.TagColumn( url_name='dcim:poweroutlet_list' ) @@ -520,10 +521,10 @@ class Meta(DeviceComponentTable.Meta): model = models.PowerOutlet fields = ( 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port', - 'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items', + 'color', 'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items', 'tags', 'created', 'last_updated', ) - default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description') class DevicePowerOutletTable(PowerOutletTable): @@ -540,11 +541,11 @@ class DevicePowerOutletTable(PowerOutletTable): class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.PowerOutlet fields = ( - 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description', - 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions', + 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'power_port', 'feed_leg', + 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions', ) default_columns = ( - 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', + 'pk', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description', 'cable', 'connection', ) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 1461b89c22..d5f10c6740 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -3421,9 +3421,9 @@ def setUpTestData(cls): PowerPort.objects.bulk_create(power_ports) power_outlets = ( - PowerOutlet(device=devices[0], module=modules[0], name='Power Outlet 1', label='A', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A, description='First'), - PowerOutlet(device=devices[1], module=modules[1], name='Power Outlet 2', label='B', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B, description='Second'), - PowerOutlet(device=devices[2], module=modules[2], name='Power Outlet 3', label='C', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C, description='Third'), + PowerOutlet(device=devices[0], module=modules[0], name='Power Outlet 1', label='A', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A, description='First', color='ff0000'), + PowerOutlet(device=devices[1], module=modules[1], name='Power Outlet 2', label='B', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B, description='Second', color='00ff00'), + PowerOutlet(device=devices[2], module=modules[2], name='Power Outlet 3', label='C', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C, description='Third', color='0000ff'), ) PowerOutlet.objects.bulk_create(power_outlets) @@ -3444,6 +3444,10 @@ def test_description(self): params = {'description': ['First', 'Second']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_color(self): + params = {'color': ['ff0000', '00ff00']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_feed_leg(self): params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/templates/dcim/poweroutlet.html b/netbox/templates/dcim/poweroutlet.html index ef02ec52a1..146f6d5804 100644 --- a/netbox/templates/dcim/poweroutlet.html +++ b/netbox/templates/dcim/poweroutlet.html @@ -40,6 +40,16 @@

{% trans "Power Outlet" %}

{% trans "Description" %} {{ object.description|placeholder }} + + {% trans "Color" %} + + {% if object.color %} +   + {% else %} + {{ ''|placeholder }} + {% endif %} + + {% trans "Power Port" %} {{ object.power_port|linkify|placeholder }}