Просмотр исходного кода

Fixes #5146: Add custom fields support for cables, power panels, rack reservations, and virtual chassis

Jeremy Stretch 5 лет назад
Родитель
Сommit
0030fe1779

+ 4 - 0
docs/release-notes/version-2.10.md

@@ -4,6 +4,10 @@
 
 **NOTE:** This release completely removes support for embedded graphs.
 
+### New Features
+
+* [#5146](https://github.com/netbox-community/netbox/issues/5146) - Add custom fields support for cables, power panels, rack reservations, and virtual chassis
+
 ### Other Changes
 
 * [#4349](https://github.com/netbox-community/netbox/issues/4349) - Dropped support for embedded graphs

+ 8 - 7
netbox/dcim/api/serializers.py

@@ -168,7 +168,7 @@ class RackUnitSerializer(serializers.Serializer):
     occupied = serializers.BooleanField(read_only=True)
 
 
-class RackReservationSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
+class RackReservationSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackreservation-detail')
     rack = NestedRackSerializer()
     user = NestedUserSerializer()
@@ -176,7 +176,7 @@ class RackReservationSerializer(TaggedObjectSerializer, ValidatedModelSerializer
 
     class Meta:
         model = RackReservation
-        fields = ['id', 'url', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags']
+        fields = ['id', 'url', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags', 'custom_fields']
 
 
 class RackElevationDetailFilterSerializer(serializers.Serializer):
@@ -649,7 +649,7 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
 # Cables
 #
 
-class CableSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
+class CableSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
     termination_a_type = ContentTypeField(
         queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
@@ -667,6 +667,7 @@ class CableSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
         fields = [
             'id', 'url', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
             'termination_b_id', 'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
+            'custom_fields',
         ]
 
     def _get_termination(self, obj, side):
@@ -729,21 +730,21 @@ class InterfaceConnectionSerializer(ValidatedModelSerializer):
 # Virtual chassis
 #
 
-class VirtualChassisSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
+class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
     master = NestedDeviceSerializer(required=False)
     member_count = serializers.IntegerField(read_only=True)
 
     class Meta:
         model = VirtualChassis
-        fields = ['id', 'url', 'name', 'domain', 'master', 'tags', 'member_count']
+        fields = ['id', 'url', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
 
 
 #
 # Power panels
 #
 
-class PowerPanelSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
+class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
     site = NestedSiteSerializer()
     rack_group = NestedRackGroupSerializer(
@@ -755,7 +756,7 @@ class PowerPanelSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
 
     class Meta:
         model = PowerPanel
-        fields = ['id', 'url', 'site', 'rack_group', 'name', 'tags', 'powerfeed_count']
+        fields = ['id', 'url', 'site', 'rack_group', 'name', 'tags', 'custom_fields', 'powerfeed_count']
 
 
 class PowerFeedSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):

+ 5 - 5
netbox/dcim/forms.py

@@ -690,7 +690,7 @@ class RackElevationFilterForm(RackFilterForm):
 # Rack reservations
 #
 
-class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
+class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False
@@ -3608,7 +3608,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
         return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
 
 
-class CableForm(BootstrapMixin, forms.ModelForm):
+class CableForm(BootstrapMixin, CustomFieldModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -3919,7 +3919,7 @@ class DeviceSelectionForm(forms.Form):
     )
 
 
-class VirtualChassisCreateForm(BootstrapMixin, forms.ModelForm):
+class VirtualChassisCreateForm(BootstrapMixin, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False
@@ -3972,7 +3972,7 @@ class VirtualChassisCreateForm(BootstrapMixin, forms.ModelForm):
         return instance
 
 
-class VirtualChassisForm(BootstrapMixin, forms.ModelForm):
+class VirtualChassisForm(BootstrapMixin, CustomFieldModelForm):
     master = forms.ModelChoiceField(
         queryset=Device.objects.all(),
         required=False,
@@ -4152,7 +4152,7 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
 # Power panels
 #
 
-class PowerPanelForm(BootstrapMixin, forms.ModelForm):
+class PowerPanelForm(BootstrapMixin, CustomFieldModelForm):
     site = DynamicModelChoiceField(
         queryset=Site.objects.all()
     )

+ 23 - 0
netbox/dcim/migrations/0116_custom_field_data.py

@@ -9,6 +9,7 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        # Original CustomFieldModels
         migrations.AddField(
             model_name='device',
             name='custom_field_data',
@@ -34,4 +35,26 @@ class Migration(migrations.Migration):
             name='custom_field_data',
             field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
         ),
+
+        # Added under #5146
+        migrations.AddField(
+            model_name='cable',
+            name='custom_field_data',
+            field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
+        migrations.AddField(
+            model_name='powerpanel',
+            name='custom_field_data',
+            field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
+        migrations.AddField(
+            model_name='rackreservation',
+            name='custom_field_data',
+            field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
+        migrations.AddField(
+            model_name='virtualchassis',
+            name='custom_field_data',
+            field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
     ]

+ 4 - 4
netbox/dcim/models/devices.py

@@ -882,8 +882,8 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
 # Cables
 #
 
-@extras_features('custom_links', 'export_templates', 'webhooks')
-class Cable(ChangeLoggedModel):
+@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
+class Cable(ChangeLoggedModel, CustomFieldModel):
     """
     A physical connection between two endpoints.
     """
@@ -1168,8 +1168,8 @@ class Cable(ChangeLoggedModel):
 # Virtual chassis
 #
 
-@extras_features('custom_links', 'export_templates', 'webhooks')
-class VirtualChassis(ChangeLoggedModel):
+@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
+class VirtualChassis(ChangeLoggedModel, CustomFieldModel):
     """
     A collection of Devices which operate with a shared control plane (e.g. a switch stack).
     """

+ 2 - 2
netbox/dcim/models/power.py

@@ -22,8 +22,8 @@ __all__ = (
 # Power
 #
 
-@extras_features('custom_links', 'export_templates', 'webhooks')
-class PowerPanel(ChangeLoggedModel):
+@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
+class PowerPanel(ChangeLoggedModel, CustomFieldModel):
     """
     A distribution point for electrical power; e.g. a data center RPP.
     """

+ 2 - 2
netbox/dcim/models/racks.py

@@ -561,8 +561,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
         return 0
 
 
-@extras_features('custom_links', 'export_templates', 'webhooks')
-class RackReservation(ChangeLoggedModel):
+@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
+class RackReservation(ChangeLoggedModel, CustomFieldModel):
     """
     One or more reserved units within a Rack.
     """

+ 1 - 0
netbox/templates/dcim/cable.html

@@ -83,6 +83,7 @@
                     </tr>
                 </table>
             </div>
+            {% include 'inc/custom_fields_panel.html' with obj=cable %}
             {% include 'extras/inc/tags_panel.html' with tags=cable.tags.all url='dcim:cable_list' %}
             {% plugin_left_page cable %}
         </div>

+ 8 - 0
netbox/templates/dcim/inc/cable_form.html

@@ -32,3 +32,11 @@
         {% render_field form.tags %}
     </div>
 </div>
+{% if form.custom_fields %}
+    <div class="panel panel-default">
+        <div class="panel-heading"><strong>Custom Fields</strong></div>
+        <div class="panel-body">
+            {% render_custom_fields form %}
+        </div>
+    </div>
+{% endif %}

+ 1 - 0
netbox/templates/dcim/powerpanel.html

@@ -82,6 +82,7 @@
                 </tr>
             </table>
         </div>
+        {% include 'inc/custom_fields_panel.html' with obj=powerpanel %}
         {% include 'extras/inc/tags_panel.html' with tags=powerpanel.tags.all url='dcim:powerpanel_list' %}
         {% plugin_left_page powerpanel %}
     </div>

+ 1 - 0
netbox/templates/dcim/rackreservation.html

@@ -124,6 +124,7 @@
                 </tr>
             </table>
         </div>
+        {% include 'inc/custom_fields_panel.html' with obj=rackreservation %}
         {% include 'extras/inc/tags_panel.html' with tags=rackreservation.tags.all url='dcim:rackreservation_list' %}
         {% plugin_left_page rackreservation %}
 	</div>

+ 8 - 0
netbox/templates/dcim/rackreservation_edit.html

@@ -21,4 +21,12 @@
             {% render_field form.tenant %}
         </div>
     </div>
+    {% if form.custom_fields %}
+        <div class="panel panel-default">
+            <div class="panel-heading"><strong>Custom Fields</strong></div>
+            <div class="panel-body">
+                {% render_custom_fields form %}
+            </div>
+        </div>
+    {% endif %}
 {% endblock %}

+ 1 - 0
netbox/templates/dcim/virtualchassis.html

@@ -78,6 +78,7 @@
                 </tr>
             </table>
         </div>
+        {% include 'inc/custom_fields_panel.html' with obj=virtualchassis %}
         {% include 'extras/inc/tags_panel.html' with tags=virtualchassis.tags.all url='dcim:virtualchassis_list' %}
         {% plugin_left_page virtualchassis %}
     </div>

+ 12 - 1
netbox/templates/dcim/virtualchassis_edit.html

@@ -21,9 +21,20 @@
                 <div class="panel panel-default">
                     <div class="panel-heading"><strong>Virtual Chassis</strong></div>
                     <div class="table panel-body">
-                        {% render_form vc_form %}
+                        {% render_field vc_form.name %}
+                        {% render_field vc_form.domain %}
+                        {% render_field vc_form.master %}
+                        {% render_field vc_form.tags %}
                     </div>
                 </div>
+                {% if vc_form.custom_fields %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading"><strong>Custom Fields</strong></div>
+                        <div class="panel-body">
+                            {% render_custom_fields vc_form %}
+                        </div>
+                    </div>
+                {% endif %}
                 <div class="panel panel-default">
                     <div class="panel-heading"><strong>Members</strong></div>
                     <table class="table panel-body">