Преглед изворни кода

Establish 4 core forms in netbox.forms.base

jeremystretch пре 4 година
родитељ
комит
f4776731ec

+ 10 - 0
docs/plugins/development/forms.md

@@ -0,0 +1,10 @@
+# Forms
+
+NetBox provides several base form classes for use by plugins. These are documented below.
+
+* `NetBoxModelForm`
+* `NetBoxModelCSVForm`
+* `NetBoxModelBulkEditForm`
+* `NetBoxModelFilterSetForm`
+
+### TODO: Include forms reference

+ 1 - 0
mkdocs.yml

@@ -105,6 +105,7 @@ nav:
             - Models: 'plugins/development/models.md'
             - Views: 'plugins/development/views.md'
             - Tables: 'plugins/development/tables.md'
+            - Forms: 'plugins/development/forms.md'
             - Filter Sets: 'plugins/development/filtersets.md'
             - REST API: 'plugins/development/rest-api.md'
             - Background Tasks: 'plugins/development/background-tasks.md'

+ 5 - 5
netbox/circuits/forms/bulk_edit.py

@@ -2,7 +2,7 @@ from django import forms
 
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
+from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from utilities.forms import add_blank_choice, CommentField, DynamicModelChoiceField, SmallTextarea, StaticSelect
 
@@ -14,7 +14,7 @@ __all__ = (
 )
 
 
-class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ProviderBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Provider.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -53,7 +53,7 @@ class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ProviderNetwork.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -81,7 +81,7 @@ class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
         ]
 
 
-class CircuitTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=CircuitType.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -95,7 +95,7 @@ class CircuitTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class CircuitBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class CircuitBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Circuit.objects.all(),
         widget=forms.MultipleHiddenInput

+ 5 - 5
netbox/circuits/forms/bulk_import.py

@@ -1,6 +1,6 @@
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
-from extras.forms import CustomFieldModelCSVForm
+from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 
@@ -12,7 +12,7 @@ __all__ = (
 )
 
 
-class ProviderCSVForm(CustomFieldModelCSVForm):
+class ProviderCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -22,7 +22,7 @@ class ProviderCSVForm(CustomFieldModelCSVForm):
         )
 
 
-class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
+class ProviderNetworkCSVForm(NetBoxModelCSVForm):
     provider = CSVModelChoiceField(
         queryset=Provider.objects.all(),
         to_field_name='name',
@@ -36,7 +36,7 @@ class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
         ]
 
 
-class CircuitTypeCSVForm(CustomFieldModelCSVForm):
+class CircuitTypeCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -47,7 +47,7 @@ class CircuitTypeCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class CircuitCSVForm(CustomFieldModelCSVForm):
+class CircuitCSVForm(NetBoxModelCSVForm):
     provider = CSVModelChoiceField(
         queryset=Provider.objects.all(),
         to_field_name='name',

+ 5 - 5
netbox/circuits/forms/filtersets.py

@@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
 from dcim.models import Region, Site, SiteGroup
-from extras.forms import CustomFieldModelFilterForm
+from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
 
@@ -16,7 +16,7 @@ __all__ = (
 )
 
 
-class ProviderFilterForm(CustomFieldModelFilterForm):
+class ProviderFilterForm(NetBoxModelFilterSetForm):
     model = Provider
     field_groups = [
         ['q', 'tag'],
@@ -49,7 +49,7 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
+class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
     model = ProviderNetwork
     field_groups = (
         ('q', 'tag'),
@@ -67,12 +67,12 @@ class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class CircuitTypeFilterForm(CustomFieldModelFilterForm):
+class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
     model = CircuitType
     tag = TagFilterField(model)
 
 
-class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class CircuitFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Circuit
     field_groups = [
         ['q', 'tag'],

+ 5 - 5
netbox/circuits/forms/models.py

@@ -2,8 +2,8 @@ from django import forms
 
 from circuits.models import *
 from dcim.models import Region, Site, SiteGroup
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
+from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.forms import (
     BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
@@ -19,7 +19,7 @@ __all__ = (
 )
 
 
-class ProviderForm(CustomFieldModelForm):
+class ProviderForm(NetBoxModelForm):
     slug = SlugField()
     comments = CommentField()
     tags = DynamicModelMultipleChoiceField(
@@ -53,7 +53,7 @@ class ProviderForm(CustomFieldModelForm):
         }
 
 
-class ProviderNetworkForm(CustomFieldModelForm):
+class ProviderNetworkForm(NetBoxModelForm):
     provider = DynamicModelChoiceField(
         queryset=Provider.objects.all()
     )
@@ -73,7 +73,7 @@ class ProviderNetworkForm(CustomFieldModelForm):
         )
 
 
-class CircuitTypeForm(CustomFieldModelForm):
+class CircuitTypeForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -87,7 +87,7 @@ class CircuitTypeForm(CustomFieldModelForm):
         ]
 
 
-class CircuitForm(TenancyForm, CustomFieldModelForm):
+class CircuitForm(TenancyForm, NetBoxModelForm):
     provider = DynamicModelChoiceField(
         queryset=Provider.objects.all()
     )

+ 30 - 40
netbox/dcim/forms/bulk_edit.py

@@ -6,8 +6,8 @@ from timezone_field import TimeZoneFormField
 from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
 from ipam.models import ASN, VLAN, VRF
+from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from utilities.forms import (
     add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
@@ -57,7 +57,7 @@ __all__ = (
 )
 
 
-class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RegionBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Region.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -75,7 +75,7 @@ class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['parent', 'description']
 
 
-class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -93,7 +93,7 @@ class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['parent', 'description']
 
 
-class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class SiteBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Site.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -137,7 +137,7 @@ class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class LocationBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Location.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -166,7 +166,7 @@ class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['parent', 'tenant', 'description']
 
 
-class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=RackRole.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -183,7 +183,7 @@ class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['color', 'description']
 
 
-class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RackBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -283,7 +283,7 @@ class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=RackReservation.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -308,7 +308,7 @@ class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
         nullable_fields = []
 
 
-class ManufacturerBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -322,7 +322,7 @@ class ManufacturerBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=DeviceType.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -353,7 +353,7 @@ class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['part_number', 'airflow']
 
 
-class ModuleTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ModuleType.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -370,7 +370,7 @@ class ModuleTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['part_number']
 
 
-class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -392,7 +392,7 @@ class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['color', 'description']
 
 
-class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class PlatformBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Platform.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -415,7 +415,7 @@ class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['manufacturer', 'napalm_driver', 'description']
 
 
-class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class DeviceBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Device.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -476,7 +476,7 @@ class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class ModuleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ModuleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Module.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -502,7 +502,7 @@ class ModuleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['serial']
 
 
-class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class CableBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Cable.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -558,7 +558,7 @@ class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
             })
 
 
-class VirtualChassisBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VirtualChassis.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -572,7 +572,7 @@ class VirtualChassisBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm
         nullable_fields = ['domain']
 
 
-class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=PowerPanel.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -611,7 +611,7 @@ class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['location']
 
 
-class PowerFeedBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=PowerFeed.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -939,8 +939,7 @@ class InventoryItemTemplateBulkEditForm(BulkEditForm):
 
 class ConsolePortBulkEditForm(
     form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=ConsolePort.objects.all(),
@@ -957,8 +956,7 @@ class ConsolePortBulkEditForm(
 
 class ConsoleServerPortBulkEditForm(
     form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=ConsoleServerPort.objects.all(),
@@ -975,8 +973,7 @@ class ConsoleServerPortBulkEditForm(
 
 class PowerPortBulkEditForm(
     form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=PowerPort.objects.all(),
@@ -993,8 +990,7 @@ class PowerPortBulkEditForm(
 
 class PowerOutletBulkEditForm(
     form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=PowerOutlet.objects.all(),
@@ -1031,8 +1027,7 @@ class InterfaceBulkEditForm(
         'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
         'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
     ]),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=Interface.objects.all(),
@@ -1154,8 +1149,7 @@ class InterfaceBulkEditForm(
 
 class FrontPortBulkEditForm(
     form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=FrontPort.objects.all(),
@@ -1168,8 +1162,7 @@ class FrontPortBulkEditForm(
 
 class RearPortBulkEditForm(
     form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=RearPort.objects.all(),
@@ -1182,8 +1175,7 @@ class RearPortBulkEditForm(
 
 class ModuleBayBulkEditForm(
     form_from_model(DeviceBay, ['label', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=ModuleBay.objects.all(),
@@ -1196,8 +1188,7 @@ class ModuleBayBulkEditForm(
 
 class DeviceBayBulkEditForm(
     form_from_model(DeviceBay, ['label', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=DeviceBay.objects.all(),
@@ -1210,8 +1201,7 @@ class DeviceBayBulkEditForm(
 
 class InventoryItemBulkEditForm(
     form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
-    AddRemoveTagsForm,
-    CustomFieldModelBulkEditForm
+    NetBoxModelBulkEditForm
 ):
     pk = forms.ModelMultipleChoiceField(
         queryset=InventoryItem.objects.all(),
@@ -1234,7 +1224,7 @@ class InventoryItemBulkEditForm(
 # Device component roles
 #
 
-class InventoryItemRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=InventoryItemRole.objects.all(),
         widget=forms.MultipleHiddenInput

+ 28 - 28
netbox/dcim/forms/bulk_import.py

@@ -7,8 +7,8 @@ from django.utils.safestring import mark_safe
 from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
-from extras.forms import CustomFieldModelCSVForm
 from ipam.models import VRF
+from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField
 from virtualization.models import Cluster
@@ -46,7 +46,7 @@ __all__ = (
 )
 
 
-class RegionCSVForm(CustomFieldModelCSVForm):
+class RegionCSVForm(NetBoxModelCSVForm):
     parent = CSVModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -59,7 +59,7 @@ class RegionCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'parent', 'description')
 
 
-class SiteGroupCSVForm(CustomFieldModelCSVForm):
+class SiteGroupCSVForm(NetBoxModelCSVForm):
     parent = CSVModelChoiceField(
         queryset=SiteGroup.objects.all(),
         required=False,
@@ -72,7 +72,7 @@ class SiteGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'parent', 'description')
 
 
-class SiteCSVForm(CustomFieldModelCSVForm):
+class SiteCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
         choices=SiteStatusChoices,
         help_text='Operational status'
@@ -109,7 +109,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class LocationCSVForm(CustomFieldModelCSVForm):
+class LocationCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         to_field_name='name',
@@ -136,7 +136,7 @@ class LocationCSVForm(CustomFieldModelCSVForm):
         fields = ('site', 'parent', 'name', 'slug', 'tenant', 'description')
 
 
-class RackRoleCSVForm(CustomFieldModelCSVForm):
+class RackRoleCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -147,7 +147,7 @@ class RackRoleCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class RackCSVForm(CustomFieldModelCSVForm):
+class RackCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         to_field_name='name'
@@ -205,7 +205,7 @@ class RackCSVForm(CustomFieldModelCSVForm):
             self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
 
 
-class RackReservationCSVForm(CustomFieldModelCSVForm):
+class RackReservationCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         to_field_name='name',
@@ -255,14 +255,14 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
             self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
 
 
-class ManufacturerCSVForm(CustomFieldModelCSVForm):
+class ManufacturerCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = Manufacturer
         fields = ('name', 'slug', 'description')
 
 
-class DeviceRoleCSVForm(CustomFieldModelCSVForm):
+class DeviceRoleCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -273,7 +273,7 @@ class DeviceRoleCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class PlatformCSVForm(CustomFieldModelCSVForm):
+class PlatformCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
     manufacturer = CSVModelChoiceField(
         queryset=Manufacturer.objects.all(),
@@ -287,7 +287,7 @@ class PlatformCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description')
 
 
-class BaseDeviceCSVForm(CustomFieldModelCSVForm):
+class BaseDeviceCSVForm(NetBoxModelCSVForm):
     device_role = CSVModelChoiceField(
         queryset=DeviceRole.objects.all(),
         to_field_name='name',
@@ -403,7 +403,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
             self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
 
 
-class ModuleCSVForm(CustomFieldModelCSVForm):
+class ModuleCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -478,7 +478,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
 # Device components
 #
 
-class ConsolePortCSVForm(CustomFieldModelCSVForm):
+class ConsolePortCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -501,7 +501,7 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm):
         fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
 
 
-class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
+class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -524,7 +524,7 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
         fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
 
 
-class PowerPortCSVForm(CustomFieldModelCSVForm):
+class PowerPortCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -542,7 +542,7 @@ class PowerPortCSVForm(CustomFieldModelCSVForm):
         )
 
 
-class PowerOutletCSVForm(CustomFieldModelCSVForm):
+class PowerOutletCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -591,7 +591,7 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm):
             self.fields['power_port'].queryset = PowerPort.objects.none()
 
 
-class InterfaceCSVForm(CustomFieldModelCSVForm):
+class InterfaceCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -655,7 +655,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
             return self.cleaned_data['enabled']
 
 
-class FrontPortCSVForm(CustomFieldModelCSVForm):
+class FrontPortCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -703,7 +703,7 @@ class FrontPortCSVForm(CustomFieldModelCSVForm):
             self.fields['rear_port'].queryset = RearPort.objects.none()
 
 
-class RearPortCSVForm(CustomFieldModelCSVForm):
+class RearPortCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -721,7 +721,7 @@ class RearPortCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class ModuleBayCSVForm(CustomFieldModelCSVForm):
+class ModuleBayCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -732,7 +732,7 @@ class ModuleBayCSVForm(CustomFieldModelCSVForm):
         fields = ('device', 'name', 'label', 'position', 'description')
 
 
-class DeviceBayCSVForm(CustomFieldModelCSVForm):
+class DeviceBayCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -778,7 +778,7 @@ class DeviceBayCSVForm(CustomFieldModelCSVForm):
             self.fields['installed_device'].queryset = Interface.objects.none()
 
 
-class InventoryItemCSVForm(CustomFieldModelCSVForm):
+class InventoryItemCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name'
@@ -827,7 +827,7 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm):
 # Device component roles
 #
 
-class InventoryItemRoleCSVForm(CustomFieldModelCSVForm):
+class InventoryItemRoleCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -842,7 +842,7 @@ class InventoryItemRoleCSVForm(CustomFieldModelCSVForm):
 # Cables
 #
 
-class CableCSVForm(CustomFieldModelCSVForm):
+class CableCSVForm(NetBoxModelCSVForm):
     # Termination A
     side_a_device = CSVModelChoiceField(
         queryset=Device.objects.all(),
@@ -947,7 +947,7 @@ class CableCSVForm(CustomFieldModelCSVForm):
 # Virtual chassis
 #
 
-class VirtualChassisCSVForm(CustomFieldModelCSVForm):
+class VirtualChassisCSVForm(NetBoxModelCSVForm):
     master = CSVModelChoiceField(
         queryset=Device.objects.all(),
         to_field_name='name',
@@ -964,7 +964,7 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm):
 # Power
 #
 
-class PowerPanelCSVForm(CustomFieldModelCSVForm):
+class PowerPanelCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         to_field_name='name',
@@ -990,7 +990,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
             self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
 
 
-class PowerFeedCSVForm(CustomFieldModelCSVForm):
+class PowerFeedCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         to_field_name='name',

+ 4 - 4
netbox/dcim/forms/connections.py

@@ -1,7 +1,7 @@
 from circuits.models import Circuit, CircuitTermination, Provider
 from dcim.models import *
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
+from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
 
@@ -18,7 +18,7 @@ __all__ = (
 )
 
 
-class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
+class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
     """
     Base form for connecting a Cable to a Device component
     """
@@ -171,7 +171,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
     )
 
 
-class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
+class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
     termination_b_provider = DynamicModelChoiceField(
         queryset=Provider.objects.all(),
         label='Provider',
@@ -229,7 +229,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
         return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
 
 
-class ConnectCableToPowerFeedForm(TenancyForm, CustomFieldModelForm):
+class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
     termination_b_region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         label='Region',

+ 22 - 21
netbox/dcim/forms/filtersets.py

@@ -5,8 +5,9 @@ from django.utils.translation import gettext as _
 from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
-from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
+from extras.forms import LocalConfigContextFilterForm
 from ipam.models import ASN, VRF
+from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
     APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
@@ -52,7 +53,7 @@ __all__ = (
 )
 
 
-class DeviceComponentFilterForm(CustomFieldModelFilterForm):
+class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
     name = forms.CharField(
         required=False
     )
@@ -103,7 +104,7 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
     )
 
 
-class RegionFilterForm(CustomFieldModelFilterForm):
+class RegionFilterForm(NetBoxModelFilterSetForm):
     model = Region
     parent_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
@@ -113,7 +114,7 @@ class RegionFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class SiteGroupFilterForm(CustomFieldModelFilterForm):
+class SiteGroupFilterForm(NetBoxModelFilterSetForm):
     model = SiteGroup
     parent_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
@@ -123,7 +124,7 @@ class SiteGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class SiteFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Site
     field_groups = [
         ['q', 'tag'],
@@ -154,7 +155,7 @@ class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class LocationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Location
     field_groups = [
         ['q', 'tag'],
@@ -192,12 +193,12 @@ class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class RackRoleFilterForm(CustomFieldModelFilterForm):
+class RackRoleFilterForm(NetBoxModelFilterSetForm):
     model = RackRole
     tag = TagFilterField(model)
 
 
-class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class RackFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Rack
     field_groups = [
         ['q', 'tag'],
@@ -270,7 +271,7 @@ class RackElevationFilterForm(RackFilterForm):
     )
 
 
-class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RackReservation
     field_groups = [
         ['q', 'tag'],
@@ -308,12 +309,12 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ManufacturerFilterForm(CustomFieldModelFilterForm):
+class ManufacturerFilterForm(NetBoxModelFilterSetForm):
     model = Manufacturer
     tag = TagFilterField(model)
 
 
-class DeviceTypeFilterForm(CustomFieldModelFilterForm):
+class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
     model = DeviceType
     field_groups = [
         ['q', 'tag'],
@@ -383,7 +384,7 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ModuleTypeFilterForm(CustomFieldModelFilterForm):
+class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
     model = ModuleType
     field_groups = [
         ['q', 'tag'],
@@ -444,12 +445,12 @@ class ModuleTypeFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class DeviceRoleFilterForm(CustomFieldModelFilterForm):
+class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
     model = DeviceRole
     tag = TagFilterField(model)
 
 
-class PlatformFilterForm(CustomFieldModelFilterForm):
+class PlatformFilterForm(NetBoxModelFilterSetForm):
     model = Platform
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
@@ -459,7 +460,7 @@ class PlatformFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
+class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Device
     field_groups = [
         ['q', 'tag'],
@@ -613,7 +614,7 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
     tag = TagFilterField(model)
 
 
-class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
+class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Module
     field_groups = [
         ['q', 'tag'],
@@ -644,7 +645,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
     tag = TagFilterField(model)
 
 
-class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VirtualChassis
     field_groups = [
         ['q', 'tag'],
@@ -673,7 +674,7 @@ class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Cable
     field_groups = [
         ['q', 'tag'],
@@ -736,7 +737,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class PowerPanelFilterForm(CustomFieldModelFilterForm):
+class PowerPanelFilterForm(NetBoxModelFilterSetForm):
     model = PowerPanel
     field_groups = (
         ('q', 'tag'),
@@ -773,7 +774,7 @@ class PowerPanelFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class PowerFeedFilterForm(CustomFieldModelFilterForm):
+class PowerFeedFilterForm(NetBoxModelFilterSetForm):
     model = PowerFeed
     field_groups = [
         ['q', 'tag'],
@@ -1103,7 +1104,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
 # Device component roles
 #
 
-class InventoryItemRoleFilterForm(CustomFieldModelFilterForm):
+class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
     model = InventoryItemRole
     tag = TagFilterField(model)
 

+ 30 - 30
netbox/dcim/forms/models.py

@@ -7,9 +7,9 @@ from timezone_field import TimeZoneFormField
 from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
+from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.forms import (
     APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
@@ -72,7 +72,7 @@ Tagged (All): Implies all VLANs are available (w/optional untagged VLAN)
 """
 
 
-class RegionForm(CustomFieldModelForm):
+class RegionForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False
@@ -90,7 +90,7 @@ class RegionForm(CustomFieldModelForm):
         )
 
 
-class SiteGroupForm(CustomFieldModelForm):
+class SiteGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=SiteGroup.objects.all(),
         required=False
@@ -108,7 +108,7 @@ class SiteGroupForm(CustomFieldModelForm):
         )
 
 
-class SiteForm(TenancyForm, CustomFieldModelForm):
+class SiteForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False
@@ -173,7 +173,7 @@ class SiteForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class LocationForm(TenancyForm, CustomFieldModelForm):
+class LocationForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -221,7 +221,7 @@ class LocationForm(TenancyForm, CustomFieldModelForm):
         )
 
 
-class RackRoleForm(CustomFieldModelForm):
+class RackRoleForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -235,7 +235,7 @@ class RackRoleForm(CustomFieldModelForm):
         ]
 
 
-class RackForm(TenancyForm, CustomFieldModelForm):
+class RackForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -295,7 +295,7 @@ class RackForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class RackReservationForm(TenancyForm, CustomFieldModelForm):
+class RackReservationForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -359,7 +359,7 @@ class RackReservationForm(TenancyForm, CustomFieldModelForm):
         )
 
 
-class ManufacturerForm(CustomFieldModelForm):
+class ManufacturerForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -373,7 +373,7 @@ class ManufacturerForm(CustomFieldModelForm):
         ]
 
 
-class DeviceTypeForm(CustomFieldModelForm):
+class DeviceTypeForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all()
     )
@@ -412,7 +412,7 @@ class DeviceTypeForm(CustomFieldModelForm):
         }
 
 
-class ModuleTypeForm(CustomFieldModelForm):
+class ModuleTypeForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all()
     )
@@ -429,7 +429,7 @@ class ModuleTypeForm(CustomFieldModelForm):
         ]
 
 
-class DeviceRoleForm(CustomFieldModelForm):
+class DeviceRoleForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -443,7 +443,7 @@ class DeviceRoleForm(CustomFieldModelForm):
         ]
 
 
-class PlatformForm(CustomFieldModelForm):
+class PlatformForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all(),
         required=False
@@ -466,7 +466,7 @@ class PlatformForm(CustomFieldModelForm):
         }
 
 
-class DeviceForm(TenancyForm, CustomFieldModelForm):
+class DeviceForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -648,7 +648,7 @@ class DeviceForm(TenancyForm, CustomFieldModelForm):
             self.fields['position'].widget.choices = [(position, f'U{position}')]
 
 
-class ModuleForm(CustomFieldModelForm):
+class ModuleForm(NetBoxModelForm):
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         required=False,
@@ -688,7 +688,7 @@ class ModuleForm(CustomFieldModelForm):
         ]
 
 
-class CableForm(TenancyForm, CustomFieldModelForm):
+class CableForm(TenancyForm, NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -711,7 +711,7 @@ class CableForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class PowerPanelForm(CustomFieldModelForm):
+class PowerPanelForm(NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -755,7 +755,7 @@ class PowerPanelForm(CustomFieldModelForm):
         )
 
 
-class PowerFeedForm(CustomFieldModelForm):
+class PowerFeedForm(NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -823,7 +823,7 @@ class PowerFeedForm(CustomFieldModelForm):
 # Virtual chassis
 #
 
-class VirtualChassisForm(CustomFieldModelForm):
+class VirtualChassisForm(NetBoxModelForm):
     master = forms.ModelChoiceField(
         queryset=Device.objects.all(),
         required=False,
@@ -1120,7 +1120,7 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
 # Device components
 #
 
-class ConsolePortForm(CustomFieldModelForm):
+class ConsolePortForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1138,7 +1138,7 @@ class ConsolePortForm(CustomFieldModelForm):
         }
 
 
-class ConsoleServerPortForm(CustomFieldModelForm):
+class ConsoleServerPortForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1156,7 +1156,7 @@ class ConsoleServerPortForm(CustomFieldModelForm):
         }
 
 
-class PowerPortForm(CustomFieldModelForm):
+class PowerPortForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1174,7 +1174,7 @@ class PowerPortForm(CustomFieldModelForm):
         }
 
 
-class PowerOutletForm(CustomFieldModelForm):
+class PowerOutletForm(NetBoxModelForm):
     power_port = DynamicModelChoiceField(
         queryset=PowerPort.objects.all(),
         required=False,
@@ -1199,7 +1199,7 @@ class PowerOutletForm(CustomFieldModelForm):
         }
 
 
-class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
+class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         required=False,
@@ -1308,7 +1308,7 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
         }
 
 
-class FrontPortForm(CustomFieldModelForm):
+class FrontPortForm(NetBoxModelForm):
     rear_port = DynamicModelChoiceField(
         queryset=RearPort.objects.all(),
         query_params={
@@ -1332,7 +1332,7 @@ class FrontPortForm(CustomFieldModelForm):
         }
 
 
-class RearPortForm(CustomFieldModelForm):
+class RearPortForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1349,7 +1349,7 @@ class RearPortForm(CustomFieldModelForm):
         }
 
 
-class ModuleBayForm(CustomFieldModelForm):
+class ModuleBayForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1365,7 +1365,7 @@ class ModuleBayForm(CustomFieldModelForm):
         }
 
 
-class DeviceBayForm(CustomFieldModelForm):
+class DeviceBayForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -1401,7 +1401,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
         ).exclude(pk=device_bay.device.pk)
 
 
-class InventoryItemForm(CustomFieldModelForm):
+class InventoryItemForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=InventoryItem.objects.all(),
         required=False,
@@ -1451,7 +1451,7 @@ class InventoryItemForm(CustomFieldModelForm):
 # Device component roles
 #
 
-class InventoryItemRoleForm(CustomFieldModelForm):
+class InventoryItemRoleForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),

+ 2 - 2
netbox/dcim/forms/object_create.py

@@ -1,8 +1,8 @@
 from django import forms
 
 from dcim.models import *
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
+from netbox.forms import NetBoxModelForm
 from utilities.forms import (
     BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
 )
@@ -149,7 +149,7 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
         }
 
 
-class VirtualChassisCreateForm(CustomFieldModelForm):
+class VirtualChassisCreateForm(NetBoxModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,

+ 0 - 81
netbox/extras/forms/customfields.py

@@ -1,16 +1,8 @@
-from django import forms
 from django.contrib.contenttypes.models import ContentType
-from django.db.models import Q
 
-from extras.choices import *
 from extras.models import *
-from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
 
 __all__ = (
-    'CustomFieldModelCSVForm',
-    'CustomFieldModelBulkEditForm',
-    'CustomFieldModelFilterForm',
-    'CustomFieldModelForm',
     'CustomFieldsMixin',
 )
 
@@ -50,76 +42,3 @@ class CustomFieldsMixin:
 
             # Annotate the field in the list of CustomField form fields
             self.custom_fields[field_name] = customfield
-
-
-class CustomFieldModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
-    """
-    Extend ModelForm to include custom field support.
-    """
-    def _get_content_type(self):
-        return ContentType.objects.get_for_model(self._meta.model)
-
-    def _get_form_field(self, customfield):
-        if self.instance.pk:
-            form_field = customfield.to_form_field(set_initial=False)
-            form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
-            return form_field
-
-        return customfield.to_form_field()
-
-    def clean(self):
-
-        # Save custom field data on instance
-        for cf_name, customfield in self.custom_fields.items():
-            key = cf_name[3:]  # Strip "cf_" from field name
-            value = self.cleaned_data.get(cf_name)
-
-            # Convert "empty" values to null
-            if value in self.fields[cf_name].empty_values:
-                self.instance.custom_field_data[key] = None
-            else:
-                self.instance.custom_field_data[key] = customfield.serialize(value)
-
-        return super().clean()
-
-
-class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
-
-    def _get_form_field(self, customfield):
-        return customfield.to_form_field(for_csv_import=True)
-
-
-class CustomFieldModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
-
-    def _get_form_field(self, customfield):
-        return customfield.to_form_field(set_initial=False, enforce_required=False)
-
-    def _append_customfield_fields(self):
-        """
-        Append form fields for all CustomFields assigned to this object type.
-        """
-        for customfield in self._get_custom_fields(self._get_content_type()):
-            # Annotate non-required custom fields as nullable
-            if not customfield.required:
-                self.nullable_fields.append(customfield.name)
-
-            self.fields[customfield.name] = self._get_form_field(customfield)
-
-            # Annotate the field in the list of CustomField form fields
-            self.custom_fields[customfield.name] = customfield
-
-
-class CustomFieldModelFilterForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
-    q = forms.CharField(
-        required=False,
-        label='Search'
-    )
-
-    def _get_custom_fields(self, content_type):
-        return CustomField.objects.filter(content_types=content_type).exclude(
-            Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
-            Q(type=CustomFieldTypeChoices.TYPE_JSON)
-        )
-
-    def _get_form_field(self, customfield):
-        return customfield.to_form_field(set_initial=False, enforce_required=False)

+ 0 - 17
netbox/extras/forms/models.py

@@ -13,7 +13,6 @@ from utilities.forms import (
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 
 __all__ = (
-    'AddRemoveTagsForm',
     'ConfigContextForm',
     'CustomFieldForm',
     'CustomLinkForm',
@@ -134,22 +133,6 @@ class TagForm(BootstrapMixin, forms.ModelForm):
         )
 
 
-class AddRemoveTagsForm(forms.Form):
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        # Add add/remove tags fields
-        self.fields['add_tags'] = DynamicModelMultipleChoiceField(
-            queryset=Tag.objects.all(),
-            required=False
-        )
-        self.fields['remove_tags'] = DynamicModelMultipleChoiceField(
-            queryset=Tag.objects.all(),
-            required=False
-        )
-
-
 class ConfigContextForm(BootstrapMixin, forms.ModelForm):
     regions = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),

+ 14 - 14
netbox/ipam/forms/bulk_edit.py

@@ -1,11 +1,11 @@
 from django import forms
 
 from dcim.models import Region, Site, SiteGroup
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
 from ipam.choices import *
 from ipam.constants import *
 from ipam.models import *
 from ipam.models import ASN
+from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from utilities.forms import (
     add_blank_choice, BulkEditNullBooleanSelect, DatePicker, DynamicModelChoiceField, NumericArrayField, StaticSelect,
@@ -30,7 +30,7 @@ __all__ = (
 )
 
 
-class VRFBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VRFBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -55,7 +55,7 @@ class VRFBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class RouteTargetBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -75,7 +75,7 @@ class RouteTargetBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class RIRBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RIRBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -93,7 +93,7 @@ class RIRBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['is_private', 'description']
 
 
-class ASNBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ASNBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ASN.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -125,7 +125,7 @@ class ASNBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         }
 
 
-class AggregateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class AggregateBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Aggregate.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -156,7 +156,7 @@ class AggregateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         }
 
 
-class RoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class RoleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Role.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -173,7 +173,7 @@ class RoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class PrefixBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class PrefixBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Prefix.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -238,7 +238,7 @@ class PrefixBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class IPRangeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=IPRange.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -272,7 +272,7 @@ class IPRangeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class IPAddressBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=IPAddress.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -317,7 +317,7 @@ class IPAddressBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class FHRPGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=FHRPGroup.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -352,7 +352,7 @@ class FHRPGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['auth_type', 'auth_key', 'description']
 
 
-class VLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VLANGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -382,7 +382,7 @@ class VLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['site', 'description']
 
 
-class VLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VLANBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -434,7 +434,7 @@ class VLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class ServiceTemplateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ServiceTemplate.objects.all(),
         widget=forms.MultipleHiddenInput()

+ 15 - 15
netbox/ipam/forms/bulk_import.py

@@ -2,10 +2,10 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 
 from dcim.models import Device, Interface, Site
-from extras.forms import CustomFieldModelCSVForm
 from ipam.choices import *
 from ipam.constants import *
 from ipam.models import *
+from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
 from virtualization.models import VirtualMachine, VMInterface
@@ -28,7 +28,7 @@ __all__ = (
 )
 
 
-class VRFCSVForm(CustomFieldModelCSVForm):
+class VRFCSVForm(NetBoxModelCSVForm):
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         required=False,
@@ -41,7 +41,7 @@ class VRFCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description')
 
 
-class RouteTargetCSVForm(CustomFieldModelCSVForm):
+class RouteTargetCSVForm(NetBoxModelCSVForm):
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         required=False,
@@ -54,7 +54,7 @@ class RouteTargetCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'description', 'tenant')
 
 
-class RIRCSVForm(CustomFieldModelCSVForm):
+class RIRCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -65,7 +65,7 @@ class RIRCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class AggregateCSVForm(CustomFieldModelCSVForm):
+class AggregateCSVForm(NetBoxModelCSVForm):
     rir = CSVModelChoiceField(
         queryset=RIR.objects.all(),
         to_field_name='name',
@@ -83,7 +83,7 @@ class AggregateCSVForm(CustomFieldModelCSVForm):
         fields = ('prefix', 'rir', 'tenant', 'date_added', 'description')
 
 
-class ASNCSVForm(CustomFieldModelCSVForm):
+class ASNCSVForm(NetBoxModelCSVForm):
     rir = CSVModelChoiceField(
         queryset=RIR.objects.all(),
         to_field_name='name',
@@ -102,7 +102,7 @@ class ASNCSVForm(CustomFieldModelCSVForm):
         help_texts = {}
 
 
-class RoleCSVForm(CustomFieldModelCSVForm):
+class RoleCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -110,7 +110,7 @@ class RoleCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'weight', 'description')
 
 
-class PrefixCSVForm(CustomFieldModelCSVForm):
+class PrefixCSVForm(NetBoxModelCSVForm):
     vrf = CSVModelChoiceField(
         queryset=VRF.objects.all(),
         to_field_name='name',
@@ -174,7 +174,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
                 self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params)
 
 
-class IPRangeCSVForm(CustomFieldModelCSVForm):
+class IPRangeCSVForm(NetBoxModelCSVForm):
     vrf = CSVModelChoiceField(
         queryset=VRF.objects.all(),
         to_field_name='name',
@@ -205,7 +205,7 @@ class IPRangeCSVForm(CustomFieldModelCSVForm):
         )
 
 
-class IPAddressCSVForm(CustomFieldModelCSVForm):
+class IPAddressCSVForm(NetBoxModelCSVForm):
     vrf = CSVModelChoiceField(
         queryset=VRF.objects.all(),
         to_field_name='name',
@@ -312,7 +312,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
         return ipaddress
 
 
-class FHRPGroupCSVForm(CustomFieldModelCSVForm):
+class FHRPGroupCSVForm(NetBoxModelCSVForm):
     protocol = CSVChoiceField(
         choices=FHRPGroupProtocolChoices
     )
@@ -326,7 +326,7 @@ class FHRPGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'description')
 
 
-class VLANGroupCSVForm(CustomFieldModelCSVForm):
+class VLANGroupCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
     scope_type = CSVContentTypeField(
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
@@ -354,7 +354,7 @@ class VLANGroupCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class VLANCSVForm(CustomFieldModelCSVForm):
+class VLANCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
@@ -393,7 +393,7 @@ class VLANCSVForm(CustomFieldModelCSVForm):
         }
 
 
-class ServiceTemplateCSVForm(CustomFieldModelCSVForm):
+class ServiceTemplateCSVForm(NetBoxModelCSVForm):
     protocol = CSVChoiceField(
         choices=ServiceProtocolChoices,
         help_text='IP protocol'
@@ -404,7 +404,7 @@ class ServiceTemplateCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'protocol', 'ports', 'description')
 
 
-class ServiceCSVForm(CustomFieldModelCSVForm):
+class ServiceCSVForm(NetBoxModelCSVForm):
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         required=False,

+ 14 - 14
netbox/ipam/forms/filtersets.py

@@ -2,11 +2,11 @@ from django import forms
 from django.utils.translation import gettext as _
 
 from dcim.models import Location, Rack, Region, Site, SiteGroup
-from extras.forms import CustomFieldModelFilterForm
 from ipam.choices import *
 from ipam.constants import *
 from ipam.models import *
 from ipam.models import ASN
+from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
     add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple,
@@ -39,7 +39,7 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
 ])
 
 
-class VRFFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VRF
     field_groups = [
         ['q', 'tag'],
@@ -59,7 +59,7 @@ class VRFFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class RouteTargetFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RouteTarget
     field_groups = [
         ['q', 'tag'],
@@ -79,7 +79,7 @@ class RouteTargetFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class RIRFilterForm(CustomFieldModelFilterForm):
+class RIRFilterForm(NetBoxModelFilterSetForm):
     model = RIR
     is_private = forms.NullBooleanField(
         required=False,
@@ -91,7 +91,7 @@ class RIRFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class AggregateFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Aggregate
     field_groups = [
         ['q', 'tag'],
@@ -112,7 +112,7 @@ class AggregateFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ASNFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = ASN
     field_groups = [
         ['q'],
@@ -132,12 +132,12 @@ class ASNFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     )
 
 
-class RoleFilterForm(CustomFieldModelFilterForm):
+class RoleFilterForm(NetBoxModelFilterSetForm):
     model = Role
     tag = TagFilterField(model)
 
 
-class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Prefix
     field_groups = [
         ['q', 'tag'],
@@ -228,7 +228,7 @@ class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPRange
     field_groups = [
         ['q', 'tag'],
@@ -261,7 +261,7 @@ class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class IPAddressFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPAddress
     field_groups = [
         ['q', 'tag'],
@@ -321,7 +321,7 @@ class IPAddressFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class FHRPGroupFilterForm(CustomFieldModelFilterForm):
+class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
     model = FHRPGroup
     field_groups = (
         ('q', 'tag'),
@@ -351,7 +351,7 @@ class FHRPGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class VLANGroupFilterForm(CustomFieldModelFilterForm):
+class VLANGroupFilterForm(NetBoxModelFilterSetForm):
     field_groups = [
         ['q', 'tag'],
         ['region', 'sitegroup', 'site', 'location', 'rack'],
@@ -394,7 +394,7 @@ class VLANGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VLAN
     field_groups = [
         ['q', 'tag'],
@@ -448,7 +448,7 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ServiceTemplateFilterForm(CustomFieldModelFilterForm):
+class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
     model = ServiceTemplate
     field_groups = (
         ('q', 'tag'),

+ 16 - 16
netbox/ipam/forms/models.py

@@ -2,13 +2,13 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 
 from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from ipam.choices import *
 from ipam.constants import *
 from ipam.formfields import IPNetworkFormField
 from ipam.models import *
 from ipam.models import ASN
+from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.exceptions import PermissionsViolation
 from utilities.forms import (
@@ -39,7 +39,7 @@ __all__ = (
 )
 
 
-class VRFForm(TenancyForm, CustomFieldModelForm):
+class VRFForm(TenancyForm, NetBoxModelForm):
     import_targets = DynamicModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         required=False
@@ -72,7 +72,7 @@ class VRFForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class RouteTargetForm(TenancyForm, CustomFieldModelForm):
+class RouteTargetForm(TenancyForm, NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -89,7 +89,7 @@ class RouteTargetForm(TenancyForm, CustomFieldModelForm):
         )
 
 
-class RIRForm(CustomFieldModelForm):
+class RIRForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -103,7 +103,7 @@ class RIRForm(CustomFieldModelForm):
         ]
 
 
-class AggregateForm(TenancyForm, CustomFieldModelForm):
+class AggregateForm(TenancyForm, NetBoxModelForm):
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         label='RIR'
@@ -131,7 +131,7 @@ class AggregateForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class ASNForm(TenancyForm, CustomFieldModelForm):
+class ASNForm(TenancyForm, NetBoxModelForm):
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         label='RIR',
@@ -175,7 +175,7 @@ class ASNForm(TenancyForm, CustomFieldModelForm):
         return instance
 
 
-class RoleForm(CustomFieldModelForm):
+class RoleForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -189,7 +189,7 @@ class RoleForm(CustomFieldModelForm):
         ]
 
 
-class PrefixForm(TenancyForm, CustomFieldModelForm):
+class PrefixForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
@@ -264,7 +264,7 @@ class PrefixForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class IPRangeForm(TenancyForm, CustomFieldModelForm):
+class IPRangeForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
@@ -293,7 +293,7 @@ class IPRangeForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class IPAddressForm(TenancyForm, CustomFieldModelForm):
+class IPAddressForm(TenancyForm, NetBoxModelForm):
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         required=False,
@@ -506,7 +506,7 @@ class IPAddressForm(TenancyForm, CustomFieldModelForm):
         return ipaddress
 
 
-class IPAddressBulkAddForm(TenancyForm, CustomFieldModelForm):
+class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
@@ -540,7 +540,7 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
     )
 
 
-class FHRPGroupForm(CustomFieldModelForm):
+class FHRPGroupForm(NetBoxModelForm):
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         required=False
@@ -629,7 +629,7 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
             self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk)
 
 
-class VLANGroupForm(CustomFieldModelForm):
+class VLANGroupForm(NetBoxModelForm):
     scope_type = ContentTypeChoiceField(
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         required=False
@@ -736,7 +736,7 @@ class VLANGroupForm(CustomFieldModelForm):
             self.instance.scope_id = None
 
 
-class VLANForm(TenancyForm, CustomFieldModelForm):
+class VLANForm(TenancyForm, NetBoxModelForm):
     # VLANGroup assignment fields
     scope_type = forms.ChoiceField(
         choices=(
@@ -817,7 +817,7 @@ class VLANForm(TenancyForm, CustomFieldModelForm):
         }
 
 
-class ServiceTemplateForm(CustomFieldModelForm):
+class ServiceTemplateForm(NetBoxModelForm):
     ports = NumericArrayField(
         base_field=forms.IntegerField(
             min_value=SERVICE_PORT_MIN,
@@ -838,7 +838,7 @@ class ServiceTemplateForm(CustomFieldModelForm):
         }
 
 
-class ServiceForm(CustomFieldModelForm):
+class ServiceForm(NetBoxModelForm):
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         required=False

+ 1 - 0
netbox/netbox/forms.py → netbox/netbox/forms/__init__.py

@@ -1,6 +1,7 @@
 from django import forms
 
 from utilities.forms import BootstrapMixin
+from .base import *
 
 OBJ_TYPE_CHOICES = (
     ('', 'All Objects'),

+ 108 - 0
netbox/netbox/forms/base.py

@@ -0,0 +1,108 @@
+from django import forms
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+
+from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
+from extras.forms.customfields import CustomFieldsMixin
+from extras.models import CustomField, Tag
+from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
+from utilities.forms.fields import DynamicModelMultipleChoiceField
+
+__all__ = (
+    'NetBoxModelForm',
+    'NetBoxModelCSVForm',
+    'NetBoxModelBulkEditForm',
+    'NetBoxModelFilterSetForm',
+)
+
+
+class NetBoxModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
+    """
+    Base form for creating & editing NetBox models. Adds support for custom fields.
+    """
+    def _get_content_type(self):
+        return ContentType.objects.get_for_model(self._meta.model)
+
+    def _get_form_field(self, customfield):
+        if self.instance.pk:
+            form_field = customfield.to_form_field(set_initial=False)
+            form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
+            return form_field
+
+        return customfield.to_form_field()
+
+    def clean(self):
+
+        # Save custom field data on instance
+        for cf_name, customfield in self.custom_fields.items():
+            key = cf_name[3:]  # Strip "cf_" from field name
+            value = self.cleaned_data.get(cf_name)
+
+            # Convert "empty" values to null
+            if value in self.fields[cf_name].empty_values:
+                self.instance.custom_field_data[key] = None
+            else:
+                self.instance.custom_field_data[key] = customfield.serialize(value)
+
+        return super().clean()
+
+
+class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm):
+    """
+    Base form for creating a NetBox objects from CSV data. Used for bulk importing.
+    """
+    def _get_form_field(self, customfield):
+        return customfield.to_form_field(for_csv_import=True)
+
+
+class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
+    """
+    Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
+    fields and adding/removing tags.
+    """
+    add_tags = DynamicModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        required=False
+    )
+    remove_tags = DynamicModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        required=False
+    )
+
+    def _get_form_field(self, customfield):
+        return customfield.to_form_field(set_initial=False, enforce_required=False)
+
+    def _append_customfield_fields(self):
+        """
+        Append form fields for all CustomFields assigned to this object type.
+        """
+        for customfield in self._get_custom_fields(self._get_content_type()):
+            # Annotate non-required custom fields as nullable
+            if not customfield.required:
+                self.nullable_fields.append(customfield.name)
+
+            self.fields[customfield.name] = self._get_form_field(customfield)
+
+            # Annotate the field in the list of CustomField form fields
+            self.custom_fields[customfield.name] = customfield
+
+
+class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
+    """
+    Base form for FilerSet forms. These are used to filter object lists in the NetBox UI.
+
+    The corresponding FilterSet *must* provide a `q` filter.
+    """
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+
+    def _get_custom_fields(self, content_type):
+        return CustomField.objects.filter(content_types=content_type).exclude(
+            Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
+            Q(type=CustomFieldTypeChoices.TYPE_JSON)
+        )
+
+    def _get_form_field(self, customfield):
+        return customfield.to_form_field(set_initial=False, enforce_required=False)

+ 6 - 6
netbox/tenancy/forms/bulk_edit.py

@@ -1,6 +1,6 @@
 from django import forms
 
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
+from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import *
 from utilities.forms import DynamicModelChoiceField
 
@@ -17,7 +17,7 @@ __all__ = (
 # Tenants
 #
 
-class TenantGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -35,7 +35,7 @@ class TenantGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['parent', 'description']
 
 
-class TenantBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class TenantBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -55,7 +55,7 @@ class TenantBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
 # Contacts
 #
 
-class ContactGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ContactGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -73,7 +73,7 @@ class ContactGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['parent', 'description']
 
 
-class ContactRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ContactRole.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -87,7 +87,7 @@ class ContactRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class ContactBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ContactBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Contact.objects.all(),
         widget=forms.MultipleHiddenInput()

+ 6 - 6
netbox/tenancy/forms/bulk_import.py

@@ -1,4 +1,4 @@
-from extras.forms import CustomFieldModelCSVForm
+from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import *
 from utilities.forms import CSVModelChoiceField, SlugField
 
@@ -15,7 +15,7 @@ __all__ = (
 # Tenants
 #
 
-class TenantGroupCSVForm(CustomFieldModelCSVForm):
+class TenantGroupCSVForm(NetBoxModelCSVForm):
     parent = CSVModelChoiceField(
         queryset=TenantGroup.objects.all(),
         required=False,
@@ -29,7 +29,7 @@ class TenantGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'parent', 'description')
 
 
-class TenantCSVForm(CustomFieldModelCSVForm):
+class TenantCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
     group = CSVModelChoiceField(
         queryset=TenantGroup.objects.all(),
@@ -47,7 +47,7 @@ class TenantCSVForm(CustomFieldModelCSVForm):
 # Contacts
 #
 
-class ContactGroupCSVForm(CustomFieldModelCSVForm):
+class ContactGroupCSVForm(NetBoxModelCSVForm):
     parent = CSVModelChoiceField(
         queryset=ContactGroup.objects.all(),
         required=False,
@@ -61,7 +61,7 @@ class ContactGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'parent', 'description')
 
 
-class ContactRoleCSVForm(CustomFieldModelCSVForm):
+class ContactRoleCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -69,7 +69,7 @@ class ContactRoleCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'description')
 
 
-class ContactCSVForm(CustomFieldModelCSVForm):
+class ContactCSVForm(NetBoxModelCSVForm):
     group = CSVModelChoiceField(
         queryset=ContactGroup.objects.all(),
         required=False,

+ 6 - 6
netbox/tenancy/forms/filtersets.py

@@ -1,6 +1,6 @@
 from django.utils.translation import gettext as _
 
-from extras.forms import CustomFieldModelFilterForm
+from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.models import *
 from utilities.forms import DynamicModelMultipleChoiceField, TagFilterField
 
@@ -17,7 +17,7 @@ __all__ = (
 # Tenants
 #
 
-class TenantGroupFilterForm(CustomFieldModelFilterForm):
+class TenantGroupFilterForm(NetBoxModelFilterSetForm):
     model = TenantGroup
     parent_id = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
@@ -27,7 +27,7 @@ class TenantGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class TenantFilterForm(CustomFieldModelFilterForm):
+class TenantFilterForm(NetBoxModelFilterSetForm):
     model = Tenant
     field_groups = (
         ('q', 'tag'),
@@ -46,7 +46,7 @@ class TenantFilterForm(CustomFieldModelFilterForm):
 # Contacts
 #
 
-class ContactGroupFilterForm(CustomFieldModelFilterForm):
+class ContactGroupFilterForm(NetBoxModelFilterSetForm):
     model = ContactGroup
     parent_id = DynamicModelMultipleChoiceField(
         queryset=ContactGroup.objects.all(),
@@ -56,12 +56,12 @@ class ContactGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class ContactRoleFilterForm(CustomFieldModelFilterForm):
+class ContactRoleFilterForm(NetBoxModelFilterSetForm):
     model = ContactRole
     tag = TagFilterField(model)
 
 
-class ContactFilterForm(CustomFieldModelFilterForm):
+class ContactFilterForm(NetBoxModelFilterSetForm):
     model = Contact
     field_groups = (
         ('q', 'tag'),

+ 6 - 6
netbox/tenancy/forms/models.py

@@ -1,7 +1,7 @@
 from django import forms
 
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
+from netbox.forms import NetBoxModelForm
 from tenancy.models import *
 from utilities.forms import (
     BootstrapMixin, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, SmallTextarea,
@@ -22,7 +22,7 @@ __all__ = (
 # Tenants
 #
 
-class TenantGroupForm(CustomFieldModelForm):
+class TenantGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
         required=False
@@ -40,7 +40,7 @@ class TenantGroupForm(CustomFieldModelForm):
         ]
 
 
-class TenantForm(CustomFieldModelForm):
+class TenantForm(NetBoxModelForm):
     slug = SlugField()
     group = DynamicModelChoiceField(
         queryset=TenantGroup.objects.all(),
@@ -66,7 +66,7 @@ class TenantForm(CustomFieldModelForm):
 # Contacts
 #
 
-class ContactGroupForm(CustomFieldModelForm):
+class ContactGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=ContactGroup.objects.all(),
         required=False
@@ -82,7 +82,7 @@ class ContactGroupForm(CustomFieldModelForm):
         fields = ('parent', 'name', 'slug', 'description', 'tags')
 
 
-class ContactRoleForm(CustomFieldModelForm):
+class ContactRoleForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -94,7 +94,7 @@ class ContactRoleForm(CustomFieldModelForm):
         fields = ('name', 'slug', 'description', 'tags')
 
 
-class ContactForm(CustomFieldModelForm):
+class ContactForm(NetBoxModelForm):
     group = DynamicModelChoiceField(
         queryset=ContactGroup.objects.all(),
         required=False

+ 6 - 6
netbox/virtualization/forms/bulk_edit.py

@@ -3,8 +3,8 @@ from django import forms
 from dcim.choices import InterfaceModeChoices
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
 from ipam.models import VLAN
+from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from utilities.forms import (
     add_blank_choice, BulkEditNullBooleanSelect, BulkRenameForm, CommentField, DynamicModelChoiceField,
@@ -23,7 +23,7 @@ __all__ = (
 )
 
 
-class ClusterTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -37,7 +37,7 @@ class ClusterTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class ClusterGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -51,7 +51,7 @@ class ClusterGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['description']
 
 
-class ClusterBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class ClusterBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -95,7 +95,7 @@ class ClusterBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         ]
 
 
-class VirtualMachineBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VirtualMachine.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -150,7 +150,7 @@ class VirtualMachineBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm
         ]
 
 
-class VMInterfaceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VMInterface.objects.all(),
         widget=forms.MultipleHiddenInput()

+ 6 - 6
netbox/virtualization/forms/bulk_import.py

@@ -1,6 +1,6 @@
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Platform, Site
-from extras.forms import CustomFieldModelCSVForm
+from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from virtualization.choices import *
@@ -15,7 +15,7 @@ __all__ = (
 )
 
 
-class ClusterTypeCSVForm(CustomFieldModelCSVForm):
+class ClusterTypeCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -23,7 +23,7 @@ class ClusterTypeCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'description')
 
 
-class ClusterGroupCSVForm(CustomFieldModelCSVForm):
+class ClusterGroupCSVForm(NetBoxModelCSVForm):
     slug = SlugField()
 
     class Meta:
@@ -31,7 +31,7 @@ class ClusterGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'description')
 
 
-class ClusterCSVForm(CustomFieldModelCSVForm):
+class ClusterCSVForm(NetBoxModelCSVForm):
     type = CSVModelChoiceField(
         queryset=ClusterType.objects.all(),
         to_field_name='name',
@@ -61,7 +61,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'type', 'group', 'site', 'comments')
 
 
-class VirtualMachineCSVForm(CustomFieldModelCSVForm):
+class VirtualMachineCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
         choices=VirtualMachineStatusChoices,
         help_text='Operational status of device'
@@ -99,7 +99,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
         )
 
 
-class VMInterfaceCSVForm(CustomFieldModelCSVForm):
+class VMInterfaceCSVForm(NetBoxModelCSVForm):
     virtual_machine = CSVModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         to_field_name='name'

+ 7 - 6
netbox/virtualization/forms/filtersets.py

@@ -2,7 +2,8 @@ from django import forms
 from django.utils.translation import gettext as _
 
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
-from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
+from extras.forms import LocalConfigContextFilterForm
+from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
     DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
@@ -19,17 +20,17 @@ __all__ = (
 )
 
 
-class ClusterTypeFilterForm(CustomFieldModelFilterForm):
+class ClusterTypeFilterForm(NetBoxModelFilterSetForm):
     model = ClusterType
     tag = TagFilterField(model)
 
 
-class ClusterGroupFilterForm(CustomFieldModelFilterForm):
+class ClusterGroupFilterForm(NetBoxModelFilterSetForm):
     model = ClusterGroup
     tag = TagFilterField(model)
 
 
-class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
+class ClusterFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Cluster
     field_groups = [
         ['q', 'tag'],
@@ -71,7 +72,7 @@ class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
+class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VirtualMachine
     field_groups = [
         ['q', 'tag'],
@@ -151,7 +152,7 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
     tag = TagFilterField(model)
 
 
-class VMInterfaceFilterForm(CustomFieldModelFilterForm):
+class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
     model = VMInterface
     field_groups = [
         ['q', 'tag'],

+ 6 - 6
netbox/virtualization/forms/models.py

@@ -5,9 +5,9 @@ from django.core.exceptions import ValidationError
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
 from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from ipam.models import IPAddress, VLAN, VLANGroup
+from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.forms import (
     BootstrapMixin, CommentField, ConfirmationForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
@@ -26,7 +26,7 @@ __all__ = (
 )
 
 
-class ClusterTypeForm(CustomFieldModelForm):
+class ClusterTypeForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -40,7 +40,7 @@ class ClusterTypeForm(CustomFieldModelForm):
         )
 
 
-class ClusterGroupForm(CustomFieldModelForm):
+class ClusterGroupForm(NetBoxModelForm):
     slug = SlugField()
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -54,7 +54,7 @@ class ClusterGroupForm(CustomFieldModelForm):
         )
 
 
-class ClusterForm(TenancyForm, CustomFieldModelForm):
+class ClusterForm(TenancyForm, NetBoxModelForm):
     type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all()
     )
@@ -171,7 +171,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
     )
 
 
-class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
+class VirtualMachineForm(TenancyForm, NetBoxModelForm):
     cluster_group = DynamicModelChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,
@@ -271,7 +271,7 @@ class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
             self.fields['primary_ip6'].widget.attrs['readonly'] = True
 
 
-class VMInterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
+class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         required=False,

+ 4 - 4
netbox/wireless/forms/bulk_edit.py

@@ -1,8 +1,8 @@
 from django import forms
 
 from dcim.choices import LinkStatusChoices
-from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
 from ipam.models import VLAN
+from netbox.forms import NetBoxModelBulkEditForm
 from utilities.forms import add_blank_choice, DynamicModelChoiceField
 from wireless.choices import *
 from wireless.constants import SSID_MAX_LENGTH
@@ -15,7 +15,7 @@ __all__ = (
 )
 
 
-class WirelessLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -33,7 +33,7 @@ class WirelessLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFo
         nullable_fields = ['parent', 'description']
 
 
-class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=WirelessLAN.objects.all(),
         widget=forms.MultipleHiddenInput
@@ -72,7 +72,7 @@ class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
         nullable_fields = ['ssid', 'group', 'vlan', 'description', 'auth_type', 'auth_cipher', 'auth_psk']
 
 
-class WirelessLinkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=WirelessLink.objects.all(),
         widget=forms.MultipleHiddenInput

+ 4 - 4
netbox/wireless/forms/bulk_import.py

@@ -1,7 +1,7 @@
 from dcim.choices import LinkStatusChoices
 from dcim.models import Interface
-from extras.forms import CustomFieldModelCSVForm
 from ipam.models import VLAN
+from netbox.forms import NetBoxModelCSVForm
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from wireless.choices import *
 from wireless.models import *
@@ -13,7 +13,7 @@ __all__ = (
 )
 
 
-class WirelessLANGroupCSVForm(CustomFieldModelCSVForm):
+class WirelessLANGroupCSVForm(NetBoxModelCSVForm):
     parent = CSVModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         required=False,
@@ -27,7 +27,7 @@ class WirelessLANGroupCSVForm(CustomFieldModelCSVForm):
         fields = ('name', 'slug', 'parent', 'description')
 
 
-class WirelessLANCSVForm(CustomFieldModelCSVForm):
+class WirelessLANCSVForm(NetBoxModelCSVForm):
     group = CSVModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         required=False,
@@ -56,7 +56,7 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
         fields = ('ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk')
 
 
-class WirelessLinkCSVForm(CustomFieldModelCSVForm):
+class WirelessLinkCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
         choices=LinkStatusChoices,
         help_text='Connection status'

+ 4 - 4
netbox/wireless/forms/filtersets.py

@@ -2,7 +2,7 @@ from django import forms
 from django.utils.translation import gettext as _
 
 from dcim.choices import LinkStatusChoices
-from extras.forms import CustomFieldModelFilterForm
+from netbox.forms import NetBoxModelFilterSetForm
 from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField
 from wireless.choices import *
 from wireless.models import *
@@ -14,7 +14,7 @@ __all__ = (
 )
 
 
-class WirelessLANGroupFilterForm(CustomFieldModelFilterForm):
+class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm):
     model = WirelessLANGroup
     parent_id = DynamicModelMultipleChoiceField(
         queryset=WirelessLANGroup.objects.all(),
@@ -24,7 +24,7 @@ class WirelessLANGroupFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class WirelessLANFilterForm(CustomFieldModelFilterForm):
+class WirelessLANFilterForm(NetBoxModelFilterSetForm):
     model = WirelessLAN
     field_groups = [
         ('q', 'tag'),
@@ -56,7 +56,7 @@ class WirelessLANFilterForm(CustomFieldModelFilterForm):
     tag = TagFilterField(model)
 
 
-class WirelessLinkFilterForm(CustomFieldModelFilterForm):
+class WirelessLinkFilterForm(NetBoxModelFilterSetForm):
     model = WirelessLink
     ssid = forms.CharField(
         required=False,

+ 4 - 4
netbox/wireless/forms/models.py

@@ -1,7 +1,7 @@
 from dcim.models import Device, Interface, Location, Site
-from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from ipam.models import VLAN
+from netbox.forms import NetBoxModelForm
 from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, StaticSelect
 from wireless.models import *
 
@@ -12,7 +12,7 @@ __all__ = (
 )
 
 
-class WirelessLANGroupForm(CustomFieldModelForm):
+class WirelessLANGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         required=False
@@ -30,7 +30,7 @@ class WirelessLANGroupForm(CustomFieldModelForm):
         ]
 
 
-class WirelessLANForm(CustomFieldModelForm):
+class WirelessLANForm(NetBoxModelForm):
     group = DynamicModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         required=False
@@ -61,7 +61,7 @@ class WirelessLANForm(CustomFieldModelForm):
         }
 
 
-class WirelessLinkForm(CustomFieldModelForm):
+class WirelessLinkForm(NetBoxModelForm):
     site_a = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         required=False,