Explorar o código

17195 Add color to PowerOutlet (#17623)

* 17195 Add color to PowerOutlet

* 17195 Add color to PowerOutlet

* Reorder serializer fields

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson hai 1 ano
pai
achega
2c68aca344

+ 4 - 0
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.

+ 1 - 1
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',

+ 1 - 1
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',
         )
 
 

+ 1 - 1
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

+ 2 - 2
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')

+ 1 - 1
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)

+ 5 - 1
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):

+ 2 - 2
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',
         ]
 

+ 1 - 0
netbox/dcim/graphql/types.py

@@ -568,6 +568,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(

+ 19 - 0
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),
+        ),
+    ]

+ 4 - 0
netbox/dcim/models/device_components.py

@@ -481,6 +481,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')
 

+ 6 - 5
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 PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
         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',
         )
 
 

+ 7 - 3
netbox/dcim/tests/test_filtersets.py

@@ -3421,9 +3421,9 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
         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 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
         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)

+ 10 - 0
netbox/templates/dcim/poweroutlet.html

@@ -40,6 +40,16 @@
                         <th scope="row">{% trans "Description" %}</th>
                         <td>{{ object.description|placeholder }}</td>
                     </tr>
+                    <tr>
+                      <th scope="row">{% trans "Color" %}</th>
+                      <td>
+                        {% if object.color %}
+                          <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
+                        {% else %}
+                          {{ ''|placeholder }}
+                        {% endif %}
+                      </td>
+                    </tr>
                     <tr>
                         <th scope="row">{% trans "Power Port" %}</th>
                         <td>{{ object.power_port|linkify|placeholder }}</td>