Add custom field support for device component models

This commit is contained in:
Jeremy Stretch 2021-03-01 13:07:25 -05:00
parent 6a9b50f95d
commit d6ee4d58ba
15 changed files with 93 additions and 37 deletions

View File

@ -49,7 +49,7 @@ class CableTerminationSerializer(serializers.ModelSerializer):
return None
class ConnectedEndpointSerializer(ValidatedModelSerializer):
class ConnectedEndpointSerializer(CustomFieldModelSerializer):
connected_endpoint_type = serializers.SerializerMethodField(read_only=True)
connected_endpoint = serializers.SerializerMethodField(read_only=True)
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
@ -497,7 +497,7 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial
model = ConsoleServerPort
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
]
@ -515,7 +515,7 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer,
model = ConsolePort
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
]
@ -544,7 +544,7 @@ class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer,
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags',
'connected_endpoint_reachable', 'tags', 'custom_fields',
]
@ -563,7 +563,7 @@ class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags',
'connected_endpoint_reachable', 'tags', 'custom_fields',
]
@ -588,7 +588,7 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only',
'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
'count_ipaddresses',
]
@ -606,7 +606,7 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
return super().validate(data)
class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer):
class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
device = NestedDeviceSerializer()
type = ChoiceField(choices=PortTypeChoices)
@ -616,7 +616,7 @@ class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Val
model = RearPort
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer',
'cable_peer_type', 'tags',
'cable_peer_type', 'tags', 'custom_fields',
]
@ -631,7 +631,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'name', 'label']
class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer):
class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
device = NestedDeviceSerializer()
type = ChoiceField(choices=PortTypeChoices)
@ -642,25 +642,25 @@ class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Va
model = FrontPort
fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'tags',
'cable_peer', 'cable_peer_type', 'tags', 'custom_fields',
]
class DeviceBaySerializer(TaggedObjectSerializer, ValidatedModelSerializer):
class DeviceBaySerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
device = NestedDeviceSerializer()
installed_device = NestedDeviceSerializer(required=False, allow_null=True)
class Meta:
model = DeviceBay
fields = ['id', 'url', 'device', 'name', 'label', 'description', 'installed_device', 'tags']
fields = ['id', 'url', 'device', 'name', 'label', 'description', 'installed_device', 'tags', 'custom_fields']
#
# Inventory items
#
class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
class InventoryItemSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
device = NestedDeviceSerializer()
# Provide a default value to satisfy UniqueTogetherValidator
@ -672,7 +672,7 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
model = InventoryItem
fields = [
'id', 'url', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'discovered', 'description', 'tags', '_depth',
'discovered', 'description', 'tags', 'custom_fields', '_depth',
]

View File

@ -1,6 +1,5 @@
import django_filters
from django.contrib.auth.models import User
from django.db.models import Count
from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet
@ -704,7 +703,7 @@ class DeviceFilterSet(
return queryset.exclude(devicebays__isnull=value)
class DeviceComponentFilterSet(django_filters.FilterSet):
class DeviceComponentFilterSet(CustomFieldModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -58,7 +58,7 @@ def get_device_by_name_or_pk(name):
return device
class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm):
field_order = [
'q', 'region', 'site'
]
@ -2274,6 +2274,7 @@ class ComponentCreateForm(ComponentForm):
"""
Base form for the creation of device components (models subclassed from ComponentModel).
"""
# TODO: Enable custom field support
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
display_field='display_name'
@ -2289,6 +2290,7 @@ class ComponentCreateForm(ComponentForm):
class DeviceBulkAddComponentForm(ComponentForm):
# TODO: Enable custom field support
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
@ -2318,7 +2320,7 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class ConsolePortForm(BootstrapMixin, forms.ModelForm):
class ConsolePortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -2365,7 +2367,7 @@ class ConsolePortBulkEditForm(
nullable_fields = ('label', 'description')
class ConsolePortCSVForm(CSVModelForm):
class ConsolePortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -2396,7 +2398,7 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm):
class ConsoleServerPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -2443,7 +2445,7 @@ class ConsoleServerPortBulkEditForm(
nullable_fields = ('label', 'description')
class ConsoleServerPortCSVForm(CSVModelForm):
class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -2474,7 +2476,7 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class PowerPortForm(BootstrapMixin, forms.ModelForm):
class PowerPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -2533,7 +2535,7 @@ class PowerPortBulkEditForm(
nullable_fields = ('label', 'description')
class PowerPortCSVForm(CSVModelForm):
class PowerPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -2564,7 +2566,7 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class PowerOutletForm(BootstrapMixin, forms.ModelForm):
class PowerOutletForm(BootstrapMixin, CustomFieldModelForm):
power_port = forms.ModelChoiceField(
queryset=PowerPort.objects.all(),
required=False
@ -2658,7 +2660,7 @@ class PowerOutletBulkEditForm(
self.fields['power_port'].widget.attrs['disabled'] = True
class PowerOutletCSVForm(CSVModelForm):
class PowerOutletCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -2738,7 +2740,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class InterfaceForm(BootstrapMixin, InterfaceCommonForm, forms.ModelForm):
class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
@ -2988,7 +2990,7 @@ class InterfaceBulkEditForm(
self.cleaned_data['tagged_vlans'] = []
class InterfaceCSVForm(CSVModelForm):
class InterfaceCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -3058,7 +3060,7 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class FrontPortForm(BootstrapMixin, forms.ModelForm):
class FrontPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -3168,7 +3170,7 @@ class FrontPortBulkEditForm(
nullable_fields = ('label', 'description')
class FrontPortCSVForm(CSVModelForm):
class FrontPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -3227,7 +3229,7 @@ class RearPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class RearPortForm(BootstrapMixin, forms.ModelForm):
class RearPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -3280,7 +3282,7 @@ class RearPortBulkEditForm(
nullable_fields = ('label', 'description')
class RearPortCSVForm(CSVModelForm):
class RearPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -3307,7 +3309,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model)
class DeviceBayForm(BootstrapMixin, forms.ModelForm):
class DeviceBayForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
@ -3367,7 +3369,7 @@ class DeviceBayBulkEditForm(
nullable_fields = ('label', 'description')
class DeviceBayCSVForm(CSVModelForm):
class DeviceBayCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@ -3417,7 +3419,7 @@ class DeviceBayCSVForm(CSVModelForm):
# Inventory items
#
class InventoryItemForm(BootstrapMixin, forms.ModelForm):
class InventoryItemForm(BootstrapMixin, CustomFieldModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
display_field='display_name'
@ -3477,7 +3479,7 @@ class InventoryItemCreateForm(ComponentCreateForm):
)
class InventoryItemCSVForm(CSVModelForm):
class InventoryItemCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'

View File

@ -9,11 +9,41 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name='consoleport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='consoleserverport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='devicebay',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='devicerole',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='frontport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='interface',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='inventoryitem',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='manufacturer',
name='custom_field_data',
@ -24,6 +54,16 @@ class Migration(migrations.Migration):
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='poweroutlet',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='powerport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='rackgroup',
name='custom_field_data',
@ -34,6 +74,11 @@ class Migration(migrations.Migration):
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='rearport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='region',
name='custom_field_data',

View File

@ -13,7 +13,7 @@ from dcim.constants import *
from dcim.fields import MACAddressField
from extras.models import ObjectChange, TaggedItem
from extras.utils import extras_features
from netbox.models import BigIDModel
from netbox.models import BigIDModel, CustomFieldsMixin
from utilities.fields import NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface
@ -38,7 +38,7 @@ __all__ = (
)
class ComponentModel(BigIDModel):
class ComponentModel(CustomFieldsMixin, BigIDModel):
"""
An abstract model inherited by any model which has a parent Device.
"""

View File

@ -10,6 +10,7 @@ from utilities.utils import serialize_object
__all__ = (
'BigIDModel',
'CustomFieldsMixin',
'NestedGroupModel',
'OrganizationalModel',
'PrimaryModel',

View File

@ -34,6 +34,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -34,6 +34,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -30,6 +30,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -44,6 +44,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -66,6 +66,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -62,6 +62,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -42,6 +42,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -42,6 +42,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>

View File

@ -38,6 +38,7 @@
</tr>
</table>
</div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %}
</div>