diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 50504d99f..e95f4abb1 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -8,9 +8,9 @@ from extras.forms import ( from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( - APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelForm, DatePicker, - DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2, - StaticSelect2Multiple, TagFilterField, + APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, + CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, + StaticSelect2, StaticSelect2Multiple, TagFilterField, ) from .choices import CircuitStatusChoices from .models import Circuit, CircuitTermination, CircuitType, Provider @@ -186,7 +186,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class CircuitCSVForm(CustomFieldModelCSVForm): - provider = forms.ModelChoiceField( + provider = CSVModelChoiceField( queryset=Provider.objects.all(), to_field_name='name', help_text='Assigned provider', @@ -194,7 +194,7 @@ class CircuitCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Provider not found.' } ) - type = forms.ModelChoiceField( + type = CSVModelChoiceField( queryset=CircuitType.objects.all(), to_field_name='name', help_text='Type of circuit', @@ -207,7 +207,7 @@ class CircuitCSVForm(CustomFieldModelCSVForm): required=False, help_text='Operational status' ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c84a3bb28..88cf0d07b 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,9 +23,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant, TenantGroup from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, - BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelForm, - DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, - SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, + CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, + JSONField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup, VirtualMachine @@ -194,7 +194,7 @@ class RegionForm(BootstrapMixin, forms.ModelForm): class RegionCSVForm(CSVModelForm): - parent = forms.ModelChoiceField( + parent = CSVModelChoiceField( queryset=Region.objects.all(), required=False, to_field_name='name', @@ -273,7 +273,7 @@ class SiteCSVForm(CustomFieldModelCSVForm): required=False, help_text='Operational status' ) - region = forms.ModelChoiceField( + region = CSVModelChoiceField( queryset=Region.objects.all(), required=False, to_field_name='name', @@ -282,7 +282,7 @@ class SiteCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Region not found.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -389,7 +389,7 @@ class RackGroupForm(BootstrapMixin, forms.ModelForm): class RackGroupCSVForm(CSVModelForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', help_text='Assigned site', @@ -397,7 +397,7 @@ class RackGroupCSVForm(CSVModelForm): 'invalid_choice': 'Site not found.', } ) - parent = forms.ModelChoiceField( + parent = CSVModelChoiceField( queryset=RackGroup.objects.all(), required=False, to_field_name='name', @@ -519,14 +519,14 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class RackCSVForm(CustomFieldModelCSVForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', error_messages={ 'invalid_choice': 'Site not found.', } ) - group = forms.ModelChoiceField( + group = CSVModelChoiceField( queryset=RackGroup.objects.all(), required=False, to_field_name='name', @@ -534,7 +534,7 @@ class RackCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Rack group not found.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -548,7 +548,7 @@ class RackCSVForm(CustomFieldModelCSVForm): required=False, help_text='Operational status' ) - role = forms.ModelChoiceField( + role = CSVModelChoiceField( queryset=RackRole.objects.all(), required=False, to_field_name='name', @@ -801,7 +801,7 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm): class RackReservationCSVForm(CSVModelForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', help_text='Parent site', @@ -809,7 +809,7 @@ class RackReservationCSVForm(CSVModelForm): 'invalid_choice': 'Site not found.', } ) - rack_group = forms.ModelChoiceField( + rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, @@ -818,7 +818,7 @@ class RackReservationCSVForm(CSVModelForm): 'invalid_choice': 'Rack group not found.', } ) - rack = forms.ModelChoiceField( + rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', help_text='Rack', @@ -831,7 +831,7 @@ class RackReservationCSVForm(CSVModelForm): required=True, help_text='Comma-separated list of individual unit numbers' ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -1676,7 +1676,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm): class PlatformCSVForm(CSVModelForm): slug = SlugField() - manufacturer = forms.ModelChoiceField( + manufacturer = CSVModelChoiceField( queryset=Manufacturer.objects.all(), required=False, to_field_name='name', @@ -1890,7 +1890,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class BaseDeviceCSVForm(CustomFieldModelCSVForm): - device_role = forms.ModelChoiceField( + device_role = CSVModelChoiceField( queryset=DeviceRole.objects.all(), to_field_name='name', help_text='Assigned role', @@ -1898,7 +1898,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid device role.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -1907,7 +1907,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Tenant not found.', } ) - manufacturer = forms.ModelChoiceField( + manufacturer = CSVModelChoiceField( queryset=Manufacturer.objects.all(), to_field_name='name', help_text='Device type manufacturer', @@ -1915,7 +1915,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Manufacturer not found.', } ) - device_type = forms.ModelChoiceField( + device_type = CSVModelChoiceField( queryset=DeviceType.objects.all(), to_field_name='model', help_text='Device type model', @@ -1923,7 +1923,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Device type not found.', } ) - platform = forms.ModelChoiceField( + platform = CSVModelChoiceField( queryset=Platform.objects.all(), required=False, to_field_name='name', @@ -1936,7 +1936,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): choices=DeviceStatusChoices, help_text='Operational status' ) - cluster = forms.ModelChoiceField( + cluster = CSVModelChoiceField( queryset=Cluster.objects.all(), to_field_name='name', required=False, @@ -1961,7 +1961,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): class DeviceCSVForm(BaseDeviceCSVForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', help_text='Assigned site', @@ -1969,7 +1969,7 @@ class DeviceCSVForm(BaseDeviceCSVForm): 'invalid_choice': 'Site not found.', } ) - rack_group = forms.ModelChoiceField( + rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, @@ -1978,7 +1978,7 @@ class DeviceCSVForm(BaseDeviceCSVForm): 'invalid_choice': 'Rack group not found.', } ) - rack = forms.ModelChoiceField( + rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', required=False, @@ -2017,7 +2017,7 @@ class DeviceCSVForm(BaseDeviceCSVForm): class ChildDeviceCSVForm(BaseDeviceCSVForm): - parent = forms.ModelChoiceField( + parent = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', help_text='Parent device', @@ -2025,7 +2025,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm): 'invalid_choice': 'Parent device not found.', } ) - device_bay = forms.ModelChoiceField( + device_bay = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', help_text='Device bay in which this device is installed', @@ -2340,7 +2340,7 @@ class ConsolePortBulkEditForm( class ConsolePortCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ @@ -2443,7 +2443,7 @@ class ConsoleServerPortBulkDisconnectForm(ConfirmationForm): class ConsoleServerPortCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ @@ -2542,7 +2542,7 @@ class PowerPortBulkEditForm( class PowerPortCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ @@ -2692,14 +2692,14 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm): class PowerOutletCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ 'invalid_choice': 'Device not found.', } ) - power_port = forms.ModelChoiceField( + power_port = CSVModelChoiceField( queryset=PowerPort.objects.all(), required=False, to_field_name='name', @@ -3014,7 +3014,7 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): class InterfaceCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', @@ -3022,7 +3022,7 @@ class InterfaceCSVForm(CSVModelForm): 'invalid_choice': 'Device not found.', } ) - virtual_machine = forms.ModelChoiceField( + virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', @@ -3030,7 +3030,7 @@ class InterfaceCSVForm(CSVModelForm): 'invalid_choice': 'Virtual machine not found.', } ) - lag = forms.ModelChoiceField( + lag = CSVModelChoiceField( queryset=Interface.objects.all(), required=False, to_field_name='name', @@ -3227,14 +3227,14 @@ class FrontPortBulkDisconnectForm(ConfirmationForm): class FrontPortCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ 'invalid_choice': 'Device not found.', } ) - rear_port = forms.ModelChoiceField( + rear_port = CSVModelChoiceField( queryset=RearPort.objects.all(), to_field_name='name', help_text='Corresponding rear port', @@ -3368,7 +3368,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm): class RearPortCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ @@ -3479,14 +3479,14 @@ class DeviceBayBulkRenameForm(BulkRenameForm): class DeviceBayCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ 'invalid_choice': 'Device not found.', } ) - installed_device = forms.ModelChoiceField( + installed_device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', @@ -3771,7 +3771,7 @@ class CableForm(BootstrapMixin, forms.ModelForm): class CableCSVForm(CSVModelForm): # Termination A - side_a_device = forms.ModelChoiceField( + side_a_device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', help_text='Side A device', @@ -3779,7 +3779,7 @@ class CableCSVForm(CSVModelForm): 'invalid_choice': 'Side A device not found', } ) - side_a_type = forms.ModelChoiceField( + side_a_type = CSVModelChoiceField( queryset=ContentType.objects.all(), limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', @@ -3790,7 +3790,7 @@ class CableCSVForm(CSVModelForm): ) # Termination B - side_b_device = forms.ModelChoiceField( + side_b_device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', help_text='Side B device', @@ -3798,7 +3798,7 @@ class CableCSVForm(CSVModelForm): 'invalid_choice': 'Side B device not found', } ) - side_b_type = forms.ModelChoiceField( + side_b_type = CSVModelChoiceField( queryset=ContentType.objects.all(), limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', @@ -4124,14 +4124,14 @@ class InventoryItemCreateForm(BootstrapMixin, forms.Form): class InventoryItemCSVForm(CSVModelForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', error_messages={ 'invalid_choice': 'Device not found.', } ) - manufacturer = forms.ModelChoiceField( + manufacturer = CSVModelChoiceField( queryset=Manufacturer.objects.all(), to_field_name='name', required=False, @@ -4435,7 +4435,7 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm): class PowerPanelCSVForm(CSVModelForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', help_text='Name of parent site', @@ -4443,7 +4443,7 @@ class PowerPanelCSVForm(CSVModelForm): 'invalid_choice': 'Site not found.', } ) - rack_group = forms.ModelChoiceField( + rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), required=False, to_field_name='name', @@ -4579,7 +4579,7 @@ class PowerFeedForm(BootstrapMixin, CustomFieldModelForm): class PowerFeedCSVForm(CustomFieldModelCSVForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', help_text='Assigned site', @@ -4587,7 +4587,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Site not found.', } ) - power_panel = forms.ModelChoiceField( + power_panel = CSVModelChoiceField( queryset=PowerPanel.objects.all(), to_field_name='name', help_text='Upstream power panel', @@ -4595,7 +4595,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Power panel not found.', } ) - rack_group = forms.ModelChoiceField( + rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, @@ -4604,7 +4604,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Rack group not found.', } ) - rack = forms.ModelChoiceField( + rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', required=False, diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 2f2c99ed7..790a30f03 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -10,8 +10,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, - CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, - ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, + CSVModelChoiceField, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, + ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import VirtualMachine from .choices import * @@ -50,7 +51,7 @@ class VRFForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class VRFCSVForm(CustomFieldModelCSVForm): - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -162,7 +163,7 @@ class AggregateForm(BootstrapMixin, CustomFieldModelForm): class AggregateCSVForm(CustomFieldModelCSVForm): - rir = forms.ModelChoiceField( + rir = CSVModelChoiceField( queryset=RIR.objects.all(), to_field_name='name', help_text='Assigned RIR', @@ -324,7 +325,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class PrefixCSVForm(CustomFieldModelCSVForm): - vrf = forms.ModelChoiceField( + vrf = CSVModelChoiceField( queryset=VRF.objects.all(), to_field_name='name', required=False, @@ -333,7 +334,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'VRF not found.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -342,7 +343,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Tenant not found.', } ) - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', @@ -351,7 +352,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Site not found.', } ) - vlan_group = forms.ModelChoiceField( + vlan_group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', @@ -360,7 +361,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'VLAN group not found.', } ) - vlan = forms.ModelChoiceField( + vlan = CSVModelChoiceField( queryset=VLAN.objects.all(), required=False, to_field_name='vid', @@ -373,7 +374,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm): choices=PrefixStatusChoices, help_text='Operational status' ) - role = forms.ModelChoiceField( + role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', @@ -716,7 +717,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class IPAddressCSVForm(CustomFieldModelCSVForm): - vrf = forms.ModelChoiceField( + vrf = CSVModelChoiceField( queryset=VRF.objects.all(), to_field_name='name', required=False, @@ -725,7 +726,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'VRF not found.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, @@ -743,7 +744,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): required=False, help_text='Functional role' ) - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', @@ -752,7 +753,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Device not found.', } ) - virtual_machine = forms.ModelChoiceField( + virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', @@ -761,7 +762,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Virtual machine not found.', } ) - interface = forms.ModelChoiceField( + interface = CSVModelChoiceField( queryset=Interface.objects.all(), required=False, to_field_name='name', @@ -974,7 +975,7 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm): class VLANGroupCSVForm(CSVModelForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', @@ -1059,7 +1060,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class VLANCSVForm(CustomFieldModelCSVForm): - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', @@ -1068,7 +1069,7 @@ class VLANCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Site not found.', } ) - group = forms.ModelChoiceField( + group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', @@ -1077,7 +1078,7 @@ class VLANCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'VLAN group not found.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, @@ -1090,7 +1091,7 @@ class VLANCSVForm(CustomFieldModelCSVForm): choices=VLANStatusChoices, help_text='Operational status' ) - role = forms.ModelChoiceField( + role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', @@ -1270,7 +1271,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): class ServiceCSVForm(CustomFieldModelCSVForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', @@ -1279,7 +1280,7 @@ class ServiceCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Device not found.', } ) - virtual_machine = forms.ModelChoiceField( + virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index ec21a48ab..5214250db 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -8,8 +8,8 @@ from extras.forms import ( AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm, ) from utilities.forms import ( - APISelectMultiple, BootstrapMixin, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, - SlugField, StaticSelect2Multiple, TagFilterField, + APISelectMultiple, BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, + DynamicModelMultipleChoiceField, SlugField, StaticSelect2Multiple, TagFilterField, ) from .constants import * from .models import Secret, SecretRole, UserKey @@ -117,7 +117,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm): class SecretCSVForm(CustomFieldModelCSVForm): - device = forms.ModelChoiceField( + device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', help_text='Assigned device', @@ -125,7 +125,7 @@ class SecretCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Device not found.', } ) - role = forms.ModelChoiceField( + role = CSVModelChoiceField( queryset=SecretRole.objects.all(), to_field_name='name', help_text='Assigned role', diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index fca8e9924..423f752c5 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -5,8 +5,8 @@ from extras.forms import ( AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm, ) from utilities.forms import ( - APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelForm, DynamicModelChoiceField, - DynamicModelMultipleChoiceField, SlugField, TagFilterField, + APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm, + DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, TagFilterField, ) from .models import Tenant, TenantGroup @@ -33,7 +33,7 @@ class TenantGroupForm(BootstrapMixin, forms.ModelForm): class TenantGroupCSVForm(CSVModelForm): - parent = forms.ModelChoiceField( + parent = CSVModelChoiceField( queryset=TenantGroup.objects.all(), required=False, to_field_name='name', @@ -73,7 +73,7 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm): class TenantCSVForm(CustomFieldModelCSVForm): slug = SlugField() - group = forms.ModelChoiceField( + group = CSVModelChoiceField( queryset=TenantGroup.objects.all(), required=False, to_field_name='name', diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 98761252d..5c841a3bc 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -8,6 +8,7 @@ import yaml from django import forms from django.conf import settings from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput +from django.core.exceptions import MultipleObjectsReturned from django.db.models import Count from django.forms import BoundField from django.forms.models import fields_for_model @@ -481,7 +482,6 @@ class CSVChoiceField(forms.ChoiceField): """ Invert the provided set of choices to take the human-friendly label as input, and return the database value. """ - def __init__(self, choices, *args, **kwargs): super().__init__(choices=choices, *args, **kwargs) self.choices = [(label, label) for value, label in unpack_grouped_choices(choices)] @@ -496,6 +496,19 @@ class CSVChoiceField(forms.ChoiceField): return self.choice_values[value] +class CSVModelChoiceField(forms.ModelChoiceField): + """ + Provides additional validation for model choices entered as CSV data. + """ + def to_python(self, value): + try: + return super().to_python(value) + except MultipleObjectsReturned as e: + raise forms.ValidationError( + f'"{value}" is not a unique value for this field; multiple objects were found' + ) + + class ExpandableNameField(forms.CharField): """ A field which allows for numeric range expansion diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index f3c5d1633..ed757171c 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -14,7 +14,7 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - CommentField, ConfirmationForm, CSVChoiceField, CSVModelForm, DynamicModelChoiceField, + CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple, TagFilterField, ) @@ -95,7 +95,7 @@ class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): class ClusterCSVForm(CustomFieldModelCSVForm): - type = forms.ModelChoiceField( + type = CSVModelChoiceField( queryset=ClusterType.objects.all(), to_field_name='name', help_text='Type of cluster', @@ -103,7 +103,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid cluster type name.', } ) - group = forms.ModelChoiceField( + group = CSVModelChoiceField( queryset=ClusterGroup.objects.all(), to_field_name='name', required=False, @@ -112,7 +112,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid cluster group name.', } ) - site = forms.ModelChoiceField( + site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', required=False, @@ -121,7 +121,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid site name.', } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, @@ -401,7 +401,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): required=False, help_text='Operational status of device' ) - cluster = forms.ModelChoiceField( + cluster = CSVModelChoiceField( queryset=Cluster.objects.all(), to_field_name='name', help_text='Assigned cluster', @@ -409,7 +409,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid cluster name.', } ) - role = forms.ModelChoiceField( + role = CSVModelChoiceField( queryset=DeviceRole.objects.filter( vm_role=True ), @@ -420,7 +420,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Invalid role name.' } ) - tenant = forms.ModelChoiceField( + tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', @@ -429,7 +429,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): 'invalid_choice': 'Tenant not found.' } ) - platform = forms.ModelChoiceField( + platform = CSVModelChoiceField( queryset=Platform.objects.all(), required=False, to_field_name='name',