ソースを参照

Add custom field support for device component models

Jeremy Stretch 5 年 前
コミット
d6ee4d58ba

+ 14 - 14
netbox/dcim/api/serializers.py

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

+ 1 - 2
netbox/dcim/filters.py

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

+ 21 - 19
netbox/dcim/forms.py

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

+ 45 - 0
netbox/dcim/migrations/0123_standardize_models.py

@@ -9,11 +9,41 @@ class Migration(migrations.Migration):
     ]
     ]
 
 
     operations = [
     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(
         migrations.AddField(
             model_name='devicerole',
             model_name='devicerole',
             name='custom_field_data',
             name='custom_field_data',
             field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
             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(
         migrations.AddField(
             model_name='manufacturer',
             model_name='manufacturer',
             name='custom_field_data',
             name='custom_field_data',
@@ -24,6 +54,16 @@ class Migration(migrations.Migration):
             name='custom_field_data',
             name='custom_field_data',
             field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
             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(
         migrations.AddField(
             model_name='rackgroup',
             model_name='rackgroup',
             name='custom_field_data',
             name='custom_field_data',
@@ -34,6 +74,11 @@ class Migration(migrations.Migration):
             name='custom_field_data',
             name='custom_field_data',
             field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
             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(
         migrations.AddField(
             model_name='region',
             model_name='region',
             name='custom_field_data',
             name='custom_field_data',

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

@@ -13,7 +13,7 @@ from dcim.constants import *
 from dcim.fields import MACAddressField
 from dcim.fields import MACAddressField
 from extras.models import ObjectChange, TaggedItem
 from extras.models import ObjectChange, TaggedItem
 from extras.utils import extras_features
 from extras.utils import extras_features
-from netbox.models import BigIDModel
+from netbox.models import BigIDModel, CustomFieldsMixin
 from utilities.fields import NaturalOrderingField
 from utilities.fields import NaturalOrderingField
 from utilities.mptt import TreeManager
 from utilities.mptt import TreeManager
 from utilities.ordering import naturalize_interface
 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.
     An abstract model inherited by any model which has a parent Device.
     """
     """

+ 1 - 0
netbox/netbox/models.py

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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