Introduce CSVModelChoiceField to provide better validation for CSV model choices

This commit is contained in:
Jeremy Stretch 2020-05-06 09:43:10 -04:00
parent 607744813a
commit 70d0a5f665
7 changed files with 113 additions and 99 deletions

View File

@ -8,9 +8,9 @@ from extras.forms import (
from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelForm, DatePicker, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField,
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField,
StaticSelect2Multiple, TagFilterField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
) )
from .choices import CircuitStatusChoices from .choices import CircuitStatusChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -186,7 +186,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class CircuitCSVForm(CustomFieldModelCSVForm): class CircuitCSVForm(CustomFieldModelCSVForm):
provider = forms.ModelChoiceField( provider = CSVModelChoiceField(
queryset=Provider.objects.all(), queryset=Provider.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned provider', help_text='Assigned provider',
@ -194,7 +194,7 @@ class CircuitCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Provider not found.' 'invalid_choice': 'Provider not found.'
} }
) )
type = forms.ModelChoiceField( type = CSVModelChoiceField(
queryset=CircuitType.objects.all(), queryset=CircuitType.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Type of circuit', help_text='Type of circuit',
@ -207,7 +207,7 @@ class CircuitCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',

View File

@ -23,9 +23,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import ( from utilities.forms import (
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelForm, BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField,
DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model,
SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, JSONField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
BOOLEAN_WITH_BLANK_CHOICES, BOOLEAN_WITH_BLANK_CHOICES,
) )
from virtualization.models import Cluster, ClusterGroup, VirtualMachine from virtualization.models import Cluster, ClusterGroup, VirtualMachine
@ -194,7 +194,7 @@ class RegionForm(BootstrapMixin, forms.ModelForm):
class RegionCSVForm(CSVModelForm): class RegionCSVForm(CSVModelForm):
parent = forms.ModelChoiceField( parent = CSVModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -273,7 +273,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
region = forms.ModelChoiceField( region = CSVModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -282,7 +282,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Region not found.', 'invalid_choice': 'Region not found.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -389,7 +389,7 @@ class RackGroupForm(BootstrapMixin, forms.ModelForm):
class RackGroupCSVForm(CSVModelForm): class RackGroupCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned site', help_text='Assigned site',
@ -397,7 +397,7 @@ class RackGroupCSVForm(CSVModelForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
parent = forms.ModelChoiceField( parent = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -519,14 +519,14 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class RackCSVForm(CustomFieldModelCSVForm): class RackCSVForm(CustomFieldModelCSVForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
group = forms.ModelChoiceField( group = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -534,7 +534,7 @@ class RackCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Rack group not found.', 'invalid_choice': 'Rack group not found.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -548,7 +548,7 @@ class RackCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
role = forms.ModelChoiceField( role = CSVModelChoiceField(
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -801,7 +801,7 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
class RackReservationCSVForm(CSVModelForm): class RackReservationCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Parent site', help_text='Parent site',
@ -809,7 +809,7 @@ class RackReservationCSVForm(CSVModelForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group = forms.ModelChoiceField( rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -818,7 +818,7 @@ class RackReservationCSVForm(CSVModelForm):
'invalid_choice': 'Rack group not found.', 'invalid_choice': 'Rack group not found.',
} }
) )
rack = forms.ModelChoiceField( rack = CSVModelChoiceField(
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Rack', help_text='Rack',
@ -831,7 +831,7 @@ class RackReservationCSVForm(CSVModelForm):
required=True, required=True,
help_text='Comma-separated list of individual unit numbers' help_text='Comma-separated list of individual unit numbers'
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1676,7 +1676,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
class PlatformCSVForm(CSVModelForm): class PlatformCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
manufacturer = forms.ModelChoiceField( manufacturer = CSVModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1890,7 +1890,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class BaseDeviceCSVForm(CustomFieldModelCSVForm): class BaseDeviceCSVForm(CustomFieldModelCSVForm):
device_role = forms.ModelChoiceField( device_role = CSVModelChoiceField(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned role', help_text='Assigned role',
@ -1898,7 +1898,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid device role.', 'invalid_choice': 'Invalid device role.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1907,7 +1907,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Tenant not found.', 'invalid_choice': 'Tenant not found.',
} }
) )
manufacturer = forms.ModelChoiceField( manufacturer = CSVModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Device type manufacturer', help_text='Device type manufacturer',
@ -1915,7 +1915,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Manufacturer not found.', 'invalid_choice': 'Manufacturer not found.',
} }
) )
device_type = forms.ModelChoiceField( device_type = CSVModelChoiceField(
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
to_field_name='model', to_field_name='model',
help_text='Device type model', help_text='Device type model',
@ -1923,7 +1923,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Device type not found.', 'invalid_choice': 'Device type not found.',
} }
) )
platform = forms.ModelChoiceField( platform = CSVModelChoiceField(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1936,7 +1936,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
choices=DeviceStatusChoices, choices=DeviceStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
cluster = forms.ModelChoiceField( cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -1961,7 +1961,7 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
class DeviceCSVForm(BaseDeviceCSVForm): class DeviceCSVForm(BaseDeviceCSVForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned site', help_text='Assigned site',
@ -1969,7 +1969,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group = forms.ModelChoiceField( rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -1978,7 +1978,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
'invalid_choice': 'Rack group not found.', 'invalid_choice': 'Rack group not found.',
} }
) )
rack = forms.ModelChoiceField( rack = CSVModelChoiceField(
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -2017,7 +2017,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
class ChildDeviceCSVForm(BaseDeviceCSVForm): class ChildDeviceCSVForm(BaseDeviceCSVForm):
parent = forms.ModelChoiceField( parent = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Parent device', help_text='Parent device',
@ -2025,7 +2025,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
'invalid_choice': 'Parent device not found.', 'invalid_choice': 'Parent device not found.',
} }
) )
device_bay = forms.ModelChoiceField( device_bay = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Device bay in which this device is installed', help_text='Device bay in which this device is installed',
@ -2340,7 +2340,7 @@ class ConsolePortBulkEditForm(
class ConsolePortCSVForm(CSVModelForm): class ConsolePortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
@ -2443,7 +2443,7 @@ class ConsoleServerPortBulkDisconnectForm(ConfirmationForm):
class ConsoleServerPortCSVForm(CSVModelForm): class ConsoleServerPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
@ -2542,7 +2542,7 @@ class PowerPortBulkEditForm(
class PowerPortCSVForm(CSVModelForm): class PowerPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
@ -2692,14 +2692,14 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm):
class PowerOutletCSVForm(CSVModelForm): class PowerOutletCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
power_port = forms.ModelChoiceField( power_port = CSVModelChoiceField(
queryset=PowerPort.objects.all(), queryset=PowerPort.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -3014,7 +3014,7 @@ class InterfaceBulkDisconnectForm(ConfirmationForm):
class InterfaceCSVForm(CSVModelForm): class InterfaceCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -3022,7 +3022,7 @@ class InterfaceCSVForm(CSVModelForm):
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
virtual_machine = forms.ModelChoiceField( virtual_machine = CSVModelChoiceField(
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -3030,7 +3030,7 @@ class InterfaceCSVForm(CSVModelForm):
'invalid_choice': 'Virtual machine not found.', 'invalid_choice': 'Virtual machine not found.',
} }
) )
lag = forms.ModelChoiceField( lag = CSVModelChoiceField(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -3227,14 +3227,14 @@ class FrontPortBulkDisconnectForm(ConfirmationForm):
class FrontPortCSVForm(CSVModelForm): class FrontPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
rear_port = forms.ModelChoiceField( rear_port = CSVModelChoiceField(
queryset=RearPort.objects.all(), queryset=RearPort.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Corresponding rear port', help_text='Corresponding rear port',
@ -3368,7 +3368,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm):
class RearPortCSVForm(CSVModelForm): class RearPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
@ -3479,14 +3479,14 @@ class DeviceBayBulkRenameForm(BulkRenameForm):
class DeviceBayCSVForm(CSVModelForm): class DeviceBayCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
installed_device = forms.ModelChoiceField( installed_device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -3771,7 +3771,7 @@ class CableForm(BootstrapMixin, forms.ModelForm):
class CableCSVForm(CSVModelForm): class CableCSVForm(CSVModelForm):
# Termination A # Termination A
side_a_device = forms.ModelChoiceField( side_a_device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Side A device', help_text='Side A device',
@ -3779,7 +3779,7 @@ class CableCSVForm(CSVModelForm):
'invalid_choice': 'Side A device not found', 'invalid_choice': 'Side A device not found',
} }
) )
side_a_type = forms.ModelChoiceField( side_a_type = CSVModelChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.all(),
limit_choices_to=CABLE_TERMINATION_MODELS, limit_choices_to=CABLE_TERMINATION_MODELS,
to_field_name='model', to_field_name='model',
@ -3790,7 +3790,7 @@ class CableCSVForm(CSVModelForm):
) )
# Termination B # Termination B
side_b_device = forms.ModelChoiceField( side_b_device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Side B device', help_text='Side B device',
@ -3798,7 +3798,7 @@ class CableCSVForm(CSVModelForm):
'invalid_choice': 'Side B device not found', 'invalid_choice': 'Side B device not found',
} }
) )
side_b_type = forms.ModelChoiceField( side_b_type = CSVModelChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.all(),
limit_choices_to=CABLE_TERMINATION_MODELS, limit_choices_to=CABLE_TERMINATION_MODELS,
to_field_name='model', to_field_name='model',
@ -4124,14 +4124,14 @@ class InventoryItemCreateForm(BootstrapMixin, forms.Form):
class InventoryItemCSVForm(CSVModelForm): class InventoryItemCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
error_messages={ error_messages={
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
manufacturer = forms.ModelChoiceField( manufacturer = CSVModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -4435,7 +4435,7 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm):
class PowerPanelCSVForm(CSVModelForm): class PowerPanelCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Name of parent site', help_text='Name of parent site',
@ -4443,7 +4443,7 @@ class PowerPanelCSVForm(CSVModelForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group = forms.ModelChoiceField( rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -4579,7 +4579,7 @@ class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
class PowerFeedCSVForm(CustomFieldModelCSVForm): class PowerFeedCSVForm(CustomFieldModelCSVForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned site', help_text='Assigned site',
@ -4587,7 +4587,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
power_panel = forms.ModelChoiceField( power_panel = CSVModelChoiceField(
queryset=PowerPanel.objects.all(), queryset=PowerPanel.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Upstream power panel', help_text='Upstream power panel',
@ -4595,7 +4595,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Power panel not found.', 'invalid_choice': 'Power panel not found.',
} }
) )
rack_group = forms.ModelChoiceField( rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -4604,7 +4604,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Rack group not found.', 'invalid_choice': 'Rack group not found.',
} }
) )
rack = forms.ModelChoiceField( rack = CSVModelChoiceField(
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,

View File

@ -10,8 +10,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField,
CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, CSVModelChoiceField, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
BOOLEAN_WITH_BLANK_CHOICES,
) )
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .choices import * from .choices import *
@ -50,7 +51,7 @@ class VRFForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class VRFCSVForm(CustomFieldModelCSVForm): class VRFCSVForm(CustomFieldModelCSVForm):
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -162,7 +163,7 @@ class AggregateForm(BootstrapMixin, CustomFieldModelForm):
class AggregateCSVForm(CustomFieldModelCSVForm): class AggregateCSVForm(CustomFieldModelCSVForm):
rir = forms.ModelChoiceField( rir = CSVModelChoiceField(
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned RIR', help_text='Assigned RIR',
@ -324,7 +325,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class PrefixCSVForm(CustomFieldModelCSVForm): class PrefixCSVForm(CustomFieldModelCSVForm):
vrf = forms.ModelChoiceField( vrf = CSVModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -333,7 +334,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'VRF not found.', 'invalid_choice': 'VRF not found.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -342,7 +343,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Tenant not found.', 'invalid_choice': 'Tenant not found.',
} }
) )
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -351,7 +352,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
vlan_group = forms.ModelChoiceField( vlan_group = CSVModelChoiceField(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -360,7 +361,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'VLAN group not found.', 'invalid_choice': 'VLAN group not found.',
} }
) )
vlan = forms.ModelChoiceField( vlan = CSVModelChoiceField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
required=False, required=False,
to_field_name='vid', to_field_name='vid',
@ -373,7 +374,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
choices=PrefixStatusChoices, choices=PrefixStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
role = forms.ModelChoiceField( role = CSVModelChoiceField(
queryset=Role.objects.all(), queryset=Role.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -716,7 +717,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class IPAddressCSVForm(CustomFieldModelCSVForm): class IPAddressCSVForm(CustomFieldModelCSVForm):
vrf = forms.ModelChoiceField( vrf = CSVModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -725,7 +726,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'VRF not found.', 'invalid_choice': 'VRF not found.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -743,7 +744,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='Functional role' help_text='Functional role'
) )
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -752,7 +753,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
virtual_machine = forms.ModelChoiceField( virtual_machine = CSVModelChoiceField(
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -761,7 +762,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Virtual machine not found.', 'invalid_choice': 'Virtual machine not found.',
} }
) )
interface = forms.ModelChoiceField( interface = CSVModelChoiceField(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -974,7 +975,7 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
class VLANGroupCSVForm(CSVModelForm): class VLANGroupCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1059,7 +1060,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class VLANCSVForm(CustomFieldModelCSVForm): class VLANCSVForm(CustomFieldModelCSVForm):
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1068,7 +1069,7 @@ class VLANCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
group = forms.ModelChoiceField( group = CSVModelChoiceField(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1077,7 +1078,7 @@ class VLANCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'VLAN group not found.', 'invalid_choice': 'VLAN group not found.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -1090,7 +1091,7 @@ class VLANCSVForm(CustomFieldModelCSVForm):
choices=VLANStatusChoices, choices=VLANStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
role = forms.ModelChoiceField( role = CSVModelChoiceField(
queryset=Role.objects.all(), queryset=Role.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1270,7 +1271,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
class ServiceCSVForm(CustomFieldModelCSVForm): class ServiceCSVForm(CustomFieldModelCSVForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -1279,7 +1280,7 @@ class ServiceCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
virtual_machine = forms.ModelChoiceField( virtual_machine = CSVModelChoiceField(
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',

View File

@ -8,8 +8,8 @@ from extras.forms import (
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm, AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm,
) )
from utilities.forms import ( from utilities.forms import (
APISelectMultiple, BootstrapMixin, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, APISelectMultiple, BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
SlugField, StaticSelect2Multiple, TagFilterField, DynamicModelMultipleChoiceField, SlugField, StaticSelect2Multiple, TagFilterField,
) )
from .constants import * from .constants import *
from .models import Secret, SecretRole, UserKey from .models import Secret, SecretRole, UserKey
@ -117,7 +117,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm):
class SecretCSVForm(CustomFieldModelCSVForm): class SecretCSVForm(CustomFieldModelCSVForm):
device = forms.ModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned device', help_text='Assigned device',
@ -125,7 +125,7 @@ class SecretCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
) )
role = forms.ModelChoiceField( role = CSVModelChoiceField(
queryset=SecretRole.objects.all(), queryset=SecretRole.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned role', help_text='Assigned role',

View File

@ -5,8 +5,8 @@ from extras.forms import (
AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm, AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm,
) )
from utilities.forms import ( from utilities.forms import (
APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelForm, DynamicModelChoiceField, APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm,
DynamicModelMultipleChoiceField, SlugField, TagFilterField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, TagFilterField,
) )
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -33,7 +33,7 @@ class TenantGroupForm(BootstrapMixin, forms.ModelForm):
class TenantGroupCSVForm(CSVModelForm): class TenantGroupCSVForm(CSVModelForm):
parent = forms.ModelChoiceField( parent = CSVModelChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -73,7 +73,7 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm):
class TenantCSVForm(CustomFieldModelCSVForm): class TenantCSVForm(CustomFieldModelCSVForm):
slug = SlugField() slug = SlugField()
group = forms.ModelChoiceField( group = CSVModelChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',

View File

@ -8,6 +8,7 @@ import yaml
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput 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.db.models import Count
from django.forms import BoundField from django.forms import BoundField
from django.forms.models import fields_for_model 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. 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): def __init__(self, choices, *args, **kwargs):
super().__init__(choices=choices, *args, **kwargs) super().__init__(choices=choices, *args, **kwargs)
self.choices = [(label, label) for value, label in unpack_grouped_choices(choices)] 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] 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): class ExpandableNameField(forms.CharField):
""" """
A field which allows for numeric range expansion A field which allows for numeric range expansion

View File

@ -14,7 +14,7 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, 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, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea,
StaticSelect2, StaticSelect2Multiple, TagFilterField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
) )
@ -95,7 +95,7 @@ class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class ClusterCSVForm(CustomFieldModelCSVForm): class ClusterCSVForm(CustomFieldModelCSVForm):
type = forms.ModelChoiceField( type = CSVModelChoiceField(
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Type of cluster', help_text='Type of cluster',
@ -103,7 +103,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid cluster type name.', 'invalid_choice': 'Invalid cluster type name.',
} }
) )
group = forms.ModelChoiceField( group = CSVModelChoiceField(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -112,7 +112,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid cluster group name.', 'invalid_choice': 'Invalid cluster group name.',
} }
) )
site = forms.ModelChoiceField( site = CSVModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -121,7 +121,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid site name.', 'invalid_choice': 'Invalid site name.',
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='name', to_field_name='name',
required=False, required=False,
@ -401,7 +401,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='Operational status of device' help_text='Operational status of device'
) )
cluster = forms.ModelChoiceField( cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned cluster', help_text='Assigned cluster',
@ -409,7 +409,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid cluster name.', 'invalid_choice': 'Invalid cluster name.',
} }
) )
role = forms.ModelChoiceField( role = CSVModelChoiceField(
queryset=DeviceRole.objects.filter( queryset=DeviceRole.objects.filter(
vm_role=True vm_role=True
), ),
@ -420,7 +420,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Invalid role name.' 'invalid_choice': 'Invalid role name.'
} }
) )
tenant = forms.ModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
@ -429,7 +429,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Tenant not found.' 'invalid_choice': 'Tenant not found.'
} }
) )
platform = forms.ModelChoiceField( platform = CSVModelChoiceField(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',