mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Fixes #5146: Add custom fields support for cables, power panels, rack reservations, and virtual chassis
This commit is contained in:
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
@ -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).
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user