Introduce CSVModelForm for dynamic CSV imports

This commit is contained in:
Jeremy Stretch 2020-05-05 16:15:09 -04:00
parent d85d963842
commit 839e999a71
12 changed files with 280 additions and 294 deletions

View File

@ -8,7 +8,7 @@ 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 (
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, DatePicker, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelForm, DatePicker,
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2,
StaticSelect2Multiple, TagFilterField, StaticSelect2Multiple, TagFilterField,
) )
@ -142,7 +142,7 @@ class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
] ]
class CircuitTypeCSVForm(forms.ModelForm): class CircuitTypeCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:

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, DynamicModelChoiceField, BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelForm,
DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SelectWithPK, SmallTextarea, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, 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
@ -193,7 +193,7 @@ class RegionForm(BootstrapMixin, forms.ModelForm):
) )
class RegionCSVForm(forms.ModelForm): class RegionCSVForm(CSVModelForm):
parent = forms.ModelChoiceField( parent = forms.ModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,
@ -388,7 +388,7 @@ class RackGroupForm(BootstrapMixin, forms.ModelForm):
) )
class RackGroupCSVForm(forms.ModelForm): class RackGroupCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
@ -461,7 +461,7 @@ class RackRoleForm(BootstrapMixin, forms.ModelForm):
] ]
class RackRoleCSVForm(forms.ModelForm): class RackRoleCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:
@ -526,8 +526,13 @@ class RackCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
group_name = forms.CharField( group = forms.ModelChoiceField(
required=False queryset=RackGroup.objects.all(),
required=False,
to_field_name='name',
error_messages={
'invalid_choice': 'Rack group not found.',
}
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
@ -571,33 +576,14 @@ class RackCSVForm(CustomFieldModelCSVForm):
model = Rack model = Rack
fields = Rack.csv_headers fields = Rack.csv_headers
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit group queryset by assigned site
group_name = self.cleaned_data.get('group_name') params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
name = self.cleaned_data.get('name') self.fields['group'].queryset = self.fields['group'].queryset.filter(**params)
facility_id = self.cleaned_data.get('facility_id')
# Validate rack group
if group_name:
try:
self.instance.group = RackGroup.objects.get(site=site, name=group_name)
except RackGroup.DoesNotExist:
raise forms.ValidationError("Rack group {} not found for site {}".format(group_name, site))
# Validate uniqueness of rack name within group
if Rack.objects.filter(group=self.instance.group, name=name).exists():
raise forms.ValidationError(
"A rack named {} already exists within group {}".format(name, group_name)
)
# Validate uniqueness of facility ID within group
if facility_id and Rack.objects.filter(group=self.instance.group, facility_id=facility_id).exists():
raise forms.ValidationError(
"A rack with the facility ID {} already exists within group {}".format(facility_id, group_name)
)
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
@ -814,21 +800,31 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
return unit_choices return unit_choices
class RackReservationCSVForm(forms.ModelForm): class RackReservationCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Parent site', help_text='Parent site',
error_messages={ error_messages={
'invalid_choice': 'Invalid site name.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group = forms.CharField( rack_group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
to_field_name='name',
required=False, required=False,
help_text="Rack's group (if any)" help_text="Rack's group (if any)",
error_messages={
'invalid_choice': 'Rack group not found.',
}
) )
rack_name = forms.CharField( rack = forms.ModelChoiceField(
help_text="Rack name" queryset=Rack.objects.all(),
to_field_name='name',
help_text='Rack',
error_messages={
'invalid_choice': 'Rack not found.',
}
) )
units = SimpleArrayField( units = SimpleArrayField(
base_field=forms.IntegerField(), base_field=forms.IntegerField(),
@ -847,27 +843,23 @@ class RackReservationCSVForm(forms.ModelForm):
class Meta: class Meta:
model = RackReservation model = RackReservation
fields = ('site', 'rack_group', 'rack_name', 'units', 'tenant', 'description') fields = ('site', 'rack_group', 'rack', 'units', 'tenant', 'description')
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit rack_group queryset by assigned site
rack_group = self.cleaned_data.get('rack_group') params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
rack_name = self.cleaned_data.get('rack_name') self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
# Validate rack # Limit rack queryset by assigned site and group
if site and rack_group and rack_name: params = {
try: f"site__{self.fields['site'].to_field_name}": data.get('site'),
self.instance.rack = Rack.objects.get(site=site, group__name=rack_group, name=rack_name) f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
except Rack.DoesNotExist: }
raise forms.ValidationError("Rack {} not found in site {} group {}".format(rack_name, site, rack_group)) self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
elif site and rack_name:
try:
self.instance.rack = Rack.objects.get(site=site, group__isnull=True, name=rack_name)
except Rack.DoesNotExist:
raise forms.ValidationError("Rack {} not found in site {} (no group)".format(rack_name, site))
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm): class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
@ -933,7 +925,7 @@ class ManufacturerForm(BootstrapMixin, forms.ModelForm):
] ]
class ManufacturerCSVForm(forms.ModelForm): class ManufacturerCSVForm(CSVModelForm):
class Meta: class Meta:
model = Manufacturer model = Manufacturer
@ -1648,7 +1640,7 @@ class DeviceRoleForm(BootstrapMixin, forms.ModelForm):
] ]
class DeviceRoleCSVForm(forms.ModelForm): class DeviceRoleCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:
@ -1682,7 +1674,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
} }
class PlatformCSVForm(forms.ModelForm): class PlatformCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
manufacturer = forms.ModelChoiceField( manufacturer = forms.ModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
@ -1920,11 +1912,16 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
to_field_name='name', to_field_name='name',
help_text='Device type manufacturer', help_text='Device type manufacturer',
error_messages={ error_messages={
'invalid_choice': 'Invalid manufacturer.', 'invalid_choice': 'Manufacturer not found.',
} }
) )
model_name = forms.CharField( device_type = forms.ModelChoiceField(
help_text='Device type model name' queryset=DeviceType.objects.all(),
to_field_name='model',
help_text='Device type model',
error_messages={
'invalid_choice': 'Device type not found.',
}
) )
platform = forms.ModelChoiceField( platform = forms.ModelChoiceField(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
@ -1953,19 +1950,14 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm):
fields = [] fields = []
model = Device model = Device
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
manufacturer = self.cleaned_data.get('manufacturer') # Limit device type queryset by manufacturer
model_name = self.cleaned_data.get('model_name') params = {f"manufacturer__{self.fields['manufacturer'].to_field_name}": data.get('manufacturer')}
self.fields['device_type'].queryset = self.fields['device_type'].queryset.filter(**params)
# Validate device type
if manufacturer and model_name:
try:
self.instance.device_type = DeviceType.objects.get(manufacturer=manufacturer, model=model_name)
except DeviceType.DoesNotExist:
raise forms.ValidationError("Device type {} {} not found".format(manufacturer, model_name))
class DeviceCSVForm(BaseDeviceCSVForm): class DeviceCSVForm(BaseDeviceCSVForm):
@ -1974,16 +1966,26 @@ class DeviceCSVForm(BaseDeviceCSVForm):
to_field_name='name', to_field_name='name',
help_text='Assigned site', help_text='Assigned site',
error_messages={ error_messages={
'invalid_choice': 'Invalid site name.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group = forms.CharField( rack_group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
to_field_name='name',
required=False, required=False,
help_text='Assigned rack\'s group (if any)' help_text="Rack's group (if any)",
error_messages={
'invalid_choice': 'Rack group not found.',
}
) )
rack_name = forms.CharField( rack = forms.ModelChoiceField(
queryset=Rack.objects.all(),
to_field_name='name',
required=False, required=False,
help_text='Name of parent rack' help_text="Assigned rack",
error_messages={
'invalid_choice': 'Rack not found.',
}
) )
face = CSVChoiceField( face = CSVChoiceField(
choices=DeviceFaceChoices, choices=DeviceFaceChoices,
@ -1993,29 +1995,25 @@ class DeviceCSVForm(BaseDeviceCSVForm):
class Meta(BaseDeviceCSVForm.Meta): class Meta(BaseDeviceCSVForm.Meta):
fields = [ fields = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status', 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'site', 'rack_group', 'rack_name', 'position', 'face', 'cluster', 'comments', 'site', 'rack_group', 'rack', 'position', 'face', 'cluster', 'comments',
] ]
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit rack_group queryset by assigned site
rack_group = self.cleaned_data.get('rack_group') params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
rack_name = self.cleaned_data.get('rack_name') self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
# Validate rack # Limit rack queryset by assigned site and group
if site and rack_group and rack_name: params = {
try: f"site__{self.fields['site'].to_field_name}": data.get('site'),
self.instance.rack = Rack.objects.get(site=site, group__name=rack_group, name=rack_name) f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
except Rack.DoesNotExist: }
raise forms.ValidationError("Rack {} not found in site {} group {}".format(rack_name, site, rack_group)) self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
elif site and rack_name:
try:
self.instance.rack = Rack.objects.get(site=site, group__isnull=True, name=rack_name)
except Rack.DoesNotExist:
raise forms.ValidationError("Rack {} not found in site {} (no group)".format(rack_name, site))
class ChildDeviceCSVForm(BaseDeviceCSVForm): class ChildDeviceCSVForm(BaseDeviceCSVForm):
@ -2027,32 +2025,29 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
'invalid_choice': 'Parent device not found.', 'invalid_choice': 'Parent device not found.',
} }
) )
device_bay_name = forms.CharField( device_bay = forms.ModelChoiceField(
help_text='Name of device bay', queryset=Device.objects.all(),
to_field_name='name',
help_text='Device bay in which this device is installed',
error_messages={
'invalid_choice': 'Devie bay not found.',
}
) )
class Meta(BaseDeviceCSVForm.Meta): class Meta(BaseDeviceCSVForm.Meta):
fields = [ fields = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status', 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'parent', 'device_bay_name', 'cluster', 'comments', 'parent', 'device_bay', 'cluster', 'comments',
] ]
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
parent = self.cleaned_data.get('parent') # Limit device bay queryset by parent device
device_bay_name = self.cleaned_data.get('device_bay_name') params = {f"device__{self.fields['parent'].to_field_name}": data.get('parent')}
self.fields['device_bay'].queryset = self.fields['device_bay'].queryset.filter(**params)
# Validate device bay
if parent and device_bay_name:
try:
self.instance.parent_bay = DeviceBay.objects.get(device=parent, name=device_bay_name)
# Inherit site and rack from parent device
self.instance.site = parent.site
self.instance.rack = parent.rack
except DeviceBay.DoesNotExist:
raise forms.ValidationError("Parent device/bay ({} {}) not found".format(parent, device_bay_name))
class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
@ -2344,7 +2339,7 @@ class ConsolePortBulkEditForm(
) )
class ConsolePortCSVForm(forms.ModelForm): class ConsolePortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -2447,7 +2442,7 @@ class ConsoleServerPortBulkDisconnectForm(ConfirmationForm):
) )
class ConsoleServerPortCSVForm(forms.ModelForm): class ConsoleServerPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -2546,7 +2541,7 @@ class PowerPortBulkEditForm(
) )
class PowerPortCSVForm(forms.ModelForm): class PowerPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -2696,7 +2691,7 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm):
) )
class PowerOutletCSVForm(forms.ModelForm): class PowerOutletCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -3018,7 +3013,7 @@ class InterfaceBulkDisconnectForm(ConfirmationForm):
) )
class InterfaceCSVForm(forms.ModelForm): class InterfaceCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
@ -3231,7 +3226,7 @@ class FrontPortBulkDisconnectForm(ConfirmationForm):
) )
class FrontPortCSVForm(forms.ModelForm): class FrontPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -3372,7 +3367,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm):
) )
class RearPortCSVForm(forms.ModelForm): class RearPortCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -3483,7 +3478,7 @@ class DeviceBayBulkRenameForm(BulkRenameForm):
) )
class DeviceBayCSVForm(forms.ModelForm): class DeviceBayCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -3774,7 +3769,7 @@ class CableForm(BootstrapMixin, forms.ModelForm):
} }
class CableCSVForm(forms.ModelForm): class CableCSVForm(CSVModelForm):
# Termination A # Termination A
side_a_device = forms.ModelChoiceField( side_a_device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
@ -4128,7 +4123,7 @@ class InventoryItemCreateForm(BootstrapMixin, forms.Form):
) )
class InventoryItemCSVForm(forms.ModelForm): class InventoryItemCSVForm(CSVModelForm):
device = forms.ModelChoiceField( device = forms.ModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
@ -4439,7 +4434,7 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm):
] ]
class PowerPanelCSVForm(forms.ModelForm): class PowerPanelCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='name', to_field_name='name',
@ -4448,30 +4443,27 @@ class PowerPanelCSVForm(forms.ModelForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
rack_group_name = forms.CharField( rack_group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
required=False, required=False,
help_text="Rack group name (optional)" to_field_name='name',
error_messages={
'invalid_choice': 'Rack group not found.',
}
) )
class Meta: class Meta:
model = PowerPanel model = PowerPanel
fields = PowerPanel.csv_headers fields = PowerPanel.csv_headers
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit group queryset by assigned site
rack_group_name = self.cleaned_data.get('rack_group_name') params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
# Validate rack group
if rack_group_name:
try:
self.instance.rack_group = RackGroup.objects.get(site=site, name=rack_group_name)
except RackGroup.DoesNotExist:
raise forms.ValidationError(
"Rack group {} not found in site {}".format(rack_group_name, site)
)
class PowerPanelBulkEditForm(BootstrapMixin, BulkEditForm): class PowerPanelBulkEditForm(BootstrapMixin, BulkEditForm):
@ -4595,7 +4587,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
panel_name = forms.ModelChoiceField( power_panel = forms.ModelChoiceField(
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',
@ -4603,13 +4595,23 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Power panel not found.', 'invalid_choice': 'Power panel not found.',
} }
) )
rack_group = forms.CharField( rack_group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
to_field_name='name',
required=False, required=False,
help_text="Assigned rack's group name" help_text="Rack's group (if any)",
error_messages={
'invalid_choice': 'Rack group not found.',
}
) )
rack_name = forms.CharField( rack = forms.ModelChoiceField(
queryset=Rack.objects.all(),
to_field_name='name',
required=False, required=False,
help_text="Assigned rack name" help_text='Rack',
error_messages={
'invalid_choice': 'Rack not found.',
}
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=PowerFeedStatusChoices, choices=PowerFeedStatusChoices,
@ -4636,32 +4638,25 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
model = PowerFeed model = PowerFeed
fields = PowerFeed.csv_headers fields = PowerFeed.csv_headers
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit power_panel queryset by site
panel_name = self.cleaned_data.get('panel_name') params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
rack_group = self.cleaned_data.get('rack_group') self.fields['power_panel'].queryset = self.fields['power_panel'].queryset.filter(**params)
rack_name = self.cleaned_data.get('rack_name')
# Validate power panel # Limit rack_group queryset by site
if panel_name: params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
try: self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
self.instance.power_panel = PowerPanel.objects.get(site=site, name=panel_name)
except Rack.DoesNotExist:
raise forms.ValidationError(
"Power panel {} not found in site {}".format(panel_name, site)
)
# Validate rack # Limit rack queryset by site and group
if rack_name: params = {
try: f"site__{self.fields['site'].to_field_name}": data.get('site'),
self.instance.rack = Rack.objects.get(site=site, group__name=rack_group, name=rack_name) f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
except Rack.DoesNotExist: }
raise forms.ValidationError( self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
"Rack {} not found in site {}, group {}".format(rack_name, site, rack_group)
)
class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):

View File

@ -523,7 +523,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'site', 'group_name', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width', 'site', 'group', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
] ]
clone_fields = [ clone_fields = [
@ -829,7 +829,7 @@ class RackReservation(ChangeLoggedModel):
def clean(self): def clean(self):
if self.units: if hasattr(self, 'rack') and self.units:
# Validate that all specified units exist in the Rack. # Validate that all specified units exist in the Rack.
invalid_units = [u for u in self.units if u not in self.rack.units] invalid_units = [u for u in self.units if u not in self.rack.units]
@ -1408,7 +1408,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status', 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', 'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
] ]
clone_fields = [ clone_fields = [
@ -1791,7 +1791,7 @@ class PowerPanel(ChangeLoggedModel):
max_length=50 max_length=50
) )
csv_headers = ['site', 'rack_group_name', 'name'] csv_headers = ['site', 'rack_group', 'name']
class Meta: class Meta:
ordering = ['site', 'name'] ordering = ['site', 'name']
@ -1898,7 +1898,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'site', 'panel_name', 'rack_group', 'rack_name', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
'amperage', 'max_utilization', 'comments', 'amperage', 'max_utilization', 'comments',
] ]
clone_fields = [ clone_fields = [

View File

@ -202,7 +202,7 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
} }
cls.csv_data = ( cls.csv_data = (
'site,rack_name,units,description', 'site,rack,units,description',
'Site 1,Rack 1,"10,11,12",Reservation 1', 'Site 1,Rack 1,"10,11,12",Reservation 1',
'Site 1,Rack 1,"13,14,15",Reservation 2', 'Site 1,Rack 1,"13,14,15",Reservation 2',
'Site 1,Rack 1,"16,17,18",Reservation 3', 'Site 1,Rack 1,"16,17,18",Reservation 3',
@ -947,7 +947,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
} }
cls.csv_data = ( cls.csv_data = (
"device_role,manufacturer,model_name,status,site,name", "device_role,manufacturer,device_type,status,site,name",
"Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 4", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 4",
"Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 5", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 5",
"Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 6", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 6",
@ -1586,7 +1586,7 @@ class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
} }
cls.csv_data = ( cls.csv_data = (
"site,rack_group_name,name", "site,rack_group,name",
"Site 1,Rack Group 1,Power Panel 4", "Site 1,Rack Group 1,Power Panel 4",
"Site 1,Rack Group 1,Power Panel 5", "Site 1,Rack Group 1,Power Panel 5",
"Site 1,Rack Group 1,Power Panel 6", "Site 1,Rack Group 1,Power Panel 6",
@ -1645,7 +1645,7 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
} }
cls.csv_data = ( cls.csv_data = (
"site,panel_name,name,voltage,amperage,max_utilization", "site,power_panel,name,voltage,amperage,max_utilization",
"Site 1,Power Panel 1,Power Feed 4,120,20,80", "Site 1,Power Panel 1,Power Feed 4,120,20,80",
"Site 1,Power Panel 1,Power Feed 5,120,20,80", "Site 1,Power Panel 1,Power Feed 5,120,20,80",
"Site 1,Power Panel 1,Power Feed 6,120,20,80", "Site 1,Power Panel 1,Power Feed 6,120,20,80",

View File

@ -8,7 +8,7 @@ from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import ( from utilities.forms import (
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect, add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
CommentField, ContentTypeSelect, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField, ContentTypeSelect, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField,
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES,
) )
from virtualization.models import Cluster, ClusterGroup from virtualization.models import Cluster, ClusterGroup
@ -89,7 +89,7 @@ class CustomFieldModelForm(forms.ModelForm):
return obj return obj
class CustomFieldModelCSVForm(CustomFieldModelForm): class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
def _append_customfield_fields(self): def _append_customfield_fields(self):

View File

@ -1,5 +1,4 @@
from django import forms from django import forms
from django.core.exceptions import MultipleObjectsReturned
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from taggit.forms import TagField from taggit.forms import TagField
@ -11,8 +10,8 @@ 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,
DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, ReturnURLForm, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField,
SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, 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 *
@ -115,7 +114,7 @@ class RIRForm(BootstrapMixin, forms.ModelForm):
] ]
class RIRCSVForm(forms.ModelForm): class RIRCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:
@ -242,7 +241,7 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
] ]
class RoleCSVForm(forms.ModelForm): class RoleCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:
@ -352,13 +351,23 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
vlan_group = forms.CharField( vlan_group = forms.ModelChoiceField(
help_text='Group name of assigned VLAN', queryset=VLANGroup.objects.all(),
required=False required=False,
to_field_name='name',
help_text="VLAN's group (if any)",
error_messages={
'invalid_choice': 'VLAN group not found.',
}
) )
vlan_vid = forms.IntegerField( vlan = forms.ModelChoiceField(
help_text='Numeric ID of assigned VLAN', queryset=VLAN.objects.all(),
required=False required=False,
to_field_name='vid',
help_text="Assigned VLAN",
error_messages={
'invalid_choice': 'VLAN not found.',
}
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=PrefixStatusChoices, choices=PrefixStatusChoices,
@ -378,39 +387,17 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
model = Prefix model = Prefix
fields = Prefix.csv_headers fields = Prefix.csv_headers
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
super().clean() if data:
site = self.cleaned_data.get('site') # Limit vlan queryset by assigned site and group
vlan_group = self.cleaned_data.get('vlan_group') params = {
vlan_vid = self.cleaned_data.get('vlan_vid') f"site__{self.fields['site'].to_field_name}": data.get('site'),
f"group__{self.fields['vlan_group'].to_field_name}": data.get('vlan_group'),
# Validate VLAN }
if vlan_group and vlan_vid: self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params)
try:
self.instance.vlan = VLAN.objects.get(site=site, group__name=vlan_group, vid=vlan_vid)
except VLAN.DoesNotExist:
if site:
raise forms.ValidationError("VLAN {} not found in site {} group {}".format(
vlan_vid, site, vlan_group
))
else:
raise forms.ValidationError("Global VLAN {} not found in group {}".format(vlan_vid, vlan_group))
except MultipleObjectsReturned:
raise forms.ValidationError(
"Multiple VLANs with VID {} found in group {}".format(vlan_vid, vlan_group)
)
elif vlan_vid:
try:
self.instance.vlan = VLAN.objects.get(site=site, group__isnull=True, vid=vlan_vid)
except VLAN.DoesNotExist:
if site:
raise forms.ValidationError("VLAN {} not found in site {}".format(vlan_vid, site))
else:
raise forms.ValidationError("Global VLAN {} not found".format(vlan_vid))
except MultipleObjectsReturned:
raise forms.ValidationError("Multiple VLANs with VID {} found".format(vlan_vid))
class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
@ -760,7 +747,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Assigned device', help_text='Parent device of assigned interface (if any)',
error_messages={ error_messages={
'invalid_choice': 'Device not found.', 'invalid_choice': 'Device not found.',
} }
@ -769,14 +756,19 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Assigned virtual machine', help_text='Parent VM of assigned interface (if any)',
error_messages={ error_messages={
'invalid_choice': 'Virtual machine not found.', 'invalid_choice': 'Virtual machine not found.',
} }
) )
interface_name = forms.CharField( interface = forms.ModelChoiceField(
help_text='Name of assigned interface', queryset=Interface.objects.all(),
required=False required=False,
to_field_name='name',
help_text='Assigned interface',
error_messages={
'invalid_choice': 'Interface not found.',
}
) )
is_primary = forms.BooleanField( is_primary = forms.BooleanField(
help_text='Make this the primary IP for the assigned device', help_text='Make this the primary IP for the assigned device',
@ -787,38 +779,34 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
model = IPAddress model = IPAddress
fields = IPAddress.csv_headers fields = IPAddress.csv_headers
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit interface queryset by assigned device or virtual machine
if data.get('device'):
params = {
f"device__{self.fields['device'].to_field_name}": data.get('device')
}
elif data.get('virtual_machine'):
params = {
f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data.get('virtual_machine')
}
else:
params = {
'device': None,
'virtual_machine': None,
}
self.fields['interface'].queryset = self.fields['interface'].queryset.filter(**params)
def clean(self): def clean(self):
super().clean() super().clean()
device = self.cleaned_data.get('device') device = self.cleaned_data.get('device')
virtual_machine = self.cleaned_data.get('virtual_machine') virtual_machine = self.cleaned_data.get('virtual_machine')
interface_name = self.cleaned_data.get('interface_name')
is_primary = self.cleaned_data.get('is_primary') is_primary = self.cleaned_data.get('is_primary')
# Validate interface
if interface_name and device:
try:
self.instance.interface = Interface.objects.get(device=device, name=interface_name)
except Interface.DoesNotExist:
raise forms.ValidationError("Invalid interface {} for device {}".format(
interface_name, device
))
elif interface_name and virtual_machine:
try:
self.instance.interface = Interface.objects.get(virtual_machine=virtual_machine, name=interface_name)
except Interface.DoesNotExist:
raise forms.ValidationError("Invalid interface {} for virtual machine {}".format(
interface_name, virtual_machine
))
elif interface_name:
raise forms.ValidationError("Interface given ({}) but parent device/virtual machine not specified".format(
interface_name
))
elif device:
raise forms.ValidationError("Device specified ({}) but interface missing".format(device))
elif virtual_machine:
raise forms.ValidationError("Virtual machine specified ({}) but interface missing".format(virtual_machine))
# Validate is_primary # Validate is_primary
if is_primary and not device and not virtual_machine: if is_primary and not device and not virtual_machine:
raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP") raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP")
@ -985,7 +973,7 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
] ]
class VLANGroupCSVForm(forms.ModelForm): class VLANGroupCSVForm(CSVModelForm):
site = forms.ModelChoiceField( site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -1080,9 +1068,14 @@ class VLANCSVForm(CustomFieldModelCSVForm):
'invalid_choice': 'Site not found.', 'invalid_choice': 'Site not found.',
} }
) )
group_name = forms.CharField( group = forms.ModelChoiceField(
help_text='Name of VLAN group', queryset=VLANGroup.objects.all(),
required=False required=False,
to_field_name='name',
help_text='Assigned VLAN group',
error_messages={
'invalid_choice': 'VLAN group not found.',
}
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
@ -1115,25 +1108,14 @@ class VLANCSVForm(CustomFieldModelCSVForm):
'name': 'VLAN name', 'name': 'VLAN name',
} }
def clean(self): def __init__(self, data=None, *args, **kwargs):
super().clean() super().__init__(data, *args, **kwargs)
site = self.cleaned_data.get('site') if data:
group_name = self.cleaned_data.get('group_name')
# Validate VLAN group # Limit vlan queryset by assigned group
if group_name: params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
try: self.fields['group'].queryset = self.fields['group'].queryset.filter(**params)
self.instance.group = VLANGroup.objects.get(site=site, name=group_name)
except VLANGroup.DoesNotExist:
if site:
raise forms.ValidationError(
"VLAN group {} not found for site {}".format(group_name, site)
)
else:
raise forms.ValidationError(
"Global VLAN group {} not found".format(group_name)
)
class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):

View File

@ -365,7 +365,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'description',
] ]
clone_fields = [ clone_fields = [
'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description',
@ -636,7 +636,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary', 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
'dns_name', 'description', 'dns_name', 'description',
] ]
clone_fields = [ clone_fields = [
@ -926,7 +926,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description'] csv_headers = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
clone_fields = [ clone_fields = [
'site', 'group', 'tenant', 'status', 'role', 'description', 'site', 'group', 'tenant', 'status', 'role', 'description',
] ]

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, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, APISelectMultiple, BootstrapMixin, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
StaticSelect2Multiple, TagFilterField, SlugField, StaticSelect2Multiple, TagFilterField,
) )
from .constants import * from .constants import *
from .models import Secret, SecretRole, UserKey from .models import Secret, SecretRole, UserKey
@ -55,7 +55,7 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
} }
class SecretRoleCSVForm(forms.ModelForm): class SecretRoleCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:

View File

@ -2,10 +2,10 @@ from django import forms
from taggit.forms import TagField from taggit.forms import TagField
from extras.forms import ( from extras.forms import (
AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm,
) )
from utilities.forms import ( from utilities.forms import (
APISelect, APISelectMultiple, BootstrapMixin, CommentField, DynamicModelChoiceField, APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelForm, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, SlugField, TagFilterField, DynamicModelMultipleChoiceField, SlugField, TagFilterField,
) )
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -32,7 +32,7 @@ class TenantGroupForm(BootstrapMixin, forms.ModelForm):
] ]
class TenantGroupCSVForm(forms.ModelForm): class TenantGroupCSVForm(CSVModelForm):
parent = forms.ModelChoiceField( parent = forms.ModelChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False, required=False,
@ -71,7 +71,7 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm):
) )
class TenantCSVForm(CustomFieldModelForm): class TenantCSVForm(CustomFieldModelCSVForm):
slug = SlugField() slug = SlugField()
group = forms.ModelChoiceField( group = forms.ModelChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),

View File

@ -712,6 +712,20 @@ class BulkEditForm(forms.Form):
self.nullable_fields = self.Meta.nullable_fields self.nullable_fields = self.Meta.nullable_fields
class CSVModelForm(forms.ModelForm):
"""
ModelForm used for the import of objects in CSV format.
"""
def __init__(self, *args, headers=None, **kwargs):
super().__init__(*args, **kwargs)
# Modify the model form to accommodate any customized to_field_name properties
if headers:
for field, to_field in headers.items():
if to_field is not None:
self.fields[field].to_field_name = to_field
class ImportForm(BootstrapMixin, forms.Form): class ImportForm(BootstrapMixin, forms.Form):
""" """
Generic form for creating an object from JSON/YAML data Generic form for creating an object from JSON/YAML data

View File

@ -593,12 +593,7 @@ class BulkImportView(GetReturnURLMixin, View):
with transaction.atomic(): with transaction.atomic():
headers, records = form.cleaned_data['csv'] headers, records = form.cleaned_data['csv']
for row, data in enumerate(records, start=1): for row, data in enumerate(records, start=1):
obj_form = self.model_form(data) obj_form = self.model_form(data, headers=headers)
# Modify the model form to accommodate any customized to_field_name properties
for field, to_field in headers.items():
if to_field is not None:
obj_form.fields[field].to_field_name = to_field
if obj_form.is_valid(): if obj_form.is_valid():
obj = self._save_obj(obj_form, request) obj = self._save_obj(obj_form, request)

View File

@ -14,9 +14,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, BulkEditForm, BulkEditNullBooleanSelect, add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
CommentField, ConfirmationForm, CSVChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, CommentField, ConfirmationForm, CSVChoiceField, CSVModelForm, DynamicModelChoiceField,
ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea,
TagFilterField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
) )
from .choices import * from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -36,7 +36,7 @@ class ClusterTypeForm(BootstrapMixin, forms.ModelForm):
] ]
class ClusterTypeCSVForm(forms.ModelForm): class ClusterTypeCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta:
@ -58,7 +58,7 @@ class ClusterGroupForm(BootstrapMixin, forms.ModelForm):
] ]
class ClusterGroupCSVForm(forms.ModelForm): class ClusterGroupCSVForm(CSVModelForm):
slug = SlugField() slug = SlugField()
class Meta: class Meta: