mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 18:08:38 -06:00
Introduce CSVModelForm for dynamic CSV imports
This commit is contained in:
parent
d85d963842
commit
839e999a71
@ -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:
|
||||||
|
@ -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):
|
||||||
|
@ -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 = [
|
||||||
|
@ -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",
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
@ -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:
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user