1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Extend DeviceType import to include related objects

This commit is contained in:
Jeremy Stretch
2019-09-20 08:58:55 -04:00
32 changed files with 606 additions and 258 deletions

View File

@ -13,7 +13,9 @@ from taggit.forms import TagField
from timezone_field import TimeZoneFormField
from circuits.models import Circuit, Provider
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from extras.forms import (
AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm, LocalConfigContextFilterForm
)
from ipam.models import IPAddress, VLAN, VLANGroup
from tenancy.forms import TenancyForm
from tenancy.forms import TenancyFilterForm
@ -55,6 +57,25 @@ def get_device_by_name_or_pk(name):
return device
class InterfaceCommonForm:
def clean(self):
super().clean()
# Validate VLAN assignments
tagged_vlans = self.cleaned_data['tagged_vlans']
# Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans:
raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned."
})
# Remove all tagged VLAN assignments from "tagged all" interfaces
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL:
self.cleaned_data['tagged_vlans'] = []
class BulkRenameForm(forms.Form):
"""
An extendable form to be used for renaming device components in bulk.
@ -789,6 +810,7 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldForm):
slug = SlugField(
slug_source='model'
)
comments = CommentField()
tags = TagField(
required=False
)
@ -832,15 +854,85 @@ class DeviceTypeCSVForm(forms.ModelForm):
}
class InterfaceTemplateImportForm(BootstrapMixin, forms.ModelForm):
name_pattern = ExpandableNameField(
label='Name'
class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
def clean_device_type(self):
data = self.cleaned_data['device_type']
# Limit fields referencing other components to the parent DeviceType
for field_name, field in self.fields.items():
if isinstance(field, forms.ModelChoiceField) and not field_name == 'device_type':
field.queryset = field.queryset.filter(device_type=data)
return data
class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = ConsolePortTemplate
fields = [
'device_type', 'name',
]
class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = ConsoleServerPortTemplate
fields = [
'device_type', 'name',
]
class PowerPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = PowerPortTemplate
fields = [
'device_type', 'name', 'maximum_draw', 'allocated_draw',
]
class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
power_port = forms.ModelChoiceField(
queryset=PowerPortTemplate.objects.all(),
to_field_name='name',
required=False
)
class Meta:
model = PowerOutletTemplate
fields = [
'device_type', 'name', 'power_port', 'feed_leg',
]
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = InterfaceTemplate
fields = [
'type', 'mgmt_only',
'device_type', 'name', 'type', 'mgmt_only',
]
class FrontPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = FrontPortTemplate
fields = [
'device_type', 'name', 'type', 'rear_port', 'rear_port_position',
]
class RearPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = RearPortTemplate
fields = [
'device_type', 'name', 'type', 'positions',
]
@ -849,18 +941,61 @@ class DeviceTypeImportForm(forms.ModelForm):
queryset=Manufacturer.objects.all(),
to_field_name='name'
)
console_ports = MultiObjectField(
form=ConsolePortTemplateImportForm,
required=False
)
console_server_ports = MultiObjectField(
form=ConsoleServerPortTemplateImportForm,
required=False
)
power_ports = MultiObjectField(
form=PowerPortTemplateImportForm,
required=False
)
power_outlets = MultiObjectField(
form=PowerOutletTemplateImportForm,
required=False
)
interfaces = MultiObjectField(
form=InterfaceTemplateImportForm,
required=False
)
front_ports = MultiObjectField(
form=FrontPortTemplateImportForm,
required=False
)
rear_ports = MultiObjectField(
form=RearPortTemplateImportForm,
required=False
)
class Meta:
model = DeviceType
fields = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'interfaces',
]
def save(self, commit=True):
instance = super().save(commit)
if commit:
# Save related components
for field_name, field in self.fields.items():
if isinstance(field, MultiObjectField):
for data in self.cleaned_data[field_name]:
data.update({
'device_type': instance.pk
})
print(data)
form = field.form(data)
if form.is_valid():
form.save()
return instance
class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
@ -1252,7 +1387,9 @@ class DeviceRoleCSVForm(forms.ModelForm):
#
class PlatformForm(BootstrapMixin, forms.ModelForm):
slug = SlugField()
slug = SlugField(
max_length=64
)
class Meta:
model = Platform
@ -1366,7 +1503,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
)
comments = CommentField()
tags = TagField(required=False)
local_context_data = JSONField(required=False)
local_context_data = JSONField(
required=False,
label=''
)
class Meta:
model = Device
@ -1706,7 +1846,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
]
class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
model = Device
field_order = [
'q', 'region', 'site', 'rack_group_id', 'rack_id', 'status', 'role', 'tenant_group', 'tenant',
@ -2139,7 +2279,26 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm):
# Interfaces
#
class InterfaceForm(BootstrapMixin, forms.ModelForm):
class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
untagged_vlan = forms.ModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelect(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
tagged_vlans = forms.ModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelectMultiple(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
tags = TagField(
required=False
)
@ -2178,112 +2337,38 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG
)
def clean(self):
super().clean()
# Validate VLAN assignments
tagged_vlans = self.cleaned_data['tagged_vlans']
# Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans:
raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned."
})
# Remove all tagged VLAN assignments from "tagged all" interfaces
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL:
self.cleaned_data['tagged_vlans'] = []
class InterfaceAssignVLANsForm(BootstrapMixin, forms.ModelForm):
vlans = forms.MultipleChoiceField(
choices=[],
label='VLANs',
widget=StaticSelect2Multiple(
attrs={
'size': 20,
}
)
)
tagged = forms.BooleanField(
required=False,
initial=True
)
class Meta:
model = Interface
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.mode == IFACE_MODE_ACCESS:
self.initial['tagged'] = False
# Find all VLANs already assigned to the interface for exclusion from the list
assigned_vlans = [v.pk for v in self.instance.tagged_vlans.all()]
if self.instance.untagged_vlan is not None:
assigned_vlans.append(self.instance.untagged_vlan.pk)
# Compile VLAN choices
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
vlan_choices = []
# Add non-grouped global VLANs
global_vlans = VLAN.objects.filter(site=None, group=None).exclude(pk__in=assigned_vlans)
global_vlans = VLAN.objects.filter(site=None, group=None)
vlan_choices.append(
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
)
# Add grouped global VLANs
for group in VLANGroup.objects.filter(site=None):
global_group_vlans = VLAN.objects.filter(group=group).exclude(pk__in=assigned_vlans)
global_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append(
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
)
site = getattr(self.instance.parent, 'site', None)
site = getattr(self.instance.device, 'site', None)
if site is not None:
# Add non-grouped site VLANs
site_vlans = VLAN.objects.filter(site=site, group=None).exclude(pk__in=assigned_vlans)
site_vlans = VLAN.objects.filter(site=site, group=None)
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
# Add grouped site VLANs
for group in VLANGroup.objects.filter(site=site):
site_group_vlans = VLAN.objects.filter(group=group).exclude(pk__in=assigned_vlans)
site_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append((
'{} / {}'.format(group.site.name, group.name),
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
self.fields['vlans'].choices = vlan_choices
def clean(self):
super().clean()
# Only untagged VLANs permitted on an access interface
if self.instance.mode == IFACE_MODE_ACCESS and len(self.cleaned_data['vlans']) > 1:
raise forms.ValidationError("Only one VLAN may be assigned to an access interface.")
# 'tagged' is required if more than one VLAN is selected
if not self.cleaned_data['tagged'] and len(self.cleaned_data['vlans']) > 1:
raise forms.ValidationError("Only one untagged VLAN may be selected.")
def save(self, *args, **kwargs):
if self.cleaned_data['tagged']:
for vlan in self.cleaned_data['vlans']:
self.instance.tagged_vlans.add(vlan)
else:
self.instance.untagged_vlan_id = self.cleaned_data['vlans'][0]
return super().save(*args, **kwargs)
self.fields['untagged_vlan'].choices = vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
class InterfaceCreateForm(ComponentForm, forms.Form):
class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
name_pattern = ExpandableNameField(
label='Name'
)
@ -2327,6 +2412,24 @@ class InterfaceCreateForm(ComponentForm, forms.Form):
tags = TagField(
required=False
)
untagged_vlan = forms.ModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelect(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
tagged_vlans = forms.ModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelectMultiple(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
def __init__(self, *args, **kwargs):
@ -2344,8 +2447,38 @@ class InterfaceCreateForm(ComponentForm, forms.Form):
else:
self.fields['lag'].queryset = Interface.objects.none()
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
vlan_choices = []
global_vlans = VLAN.objects.filter(site=None, group=None)
vlan_choices.append(
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
)
for group in VLANGroup.objects.filter(site=None):
global_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append(
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
)
class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
site = getattr(self.parent, 'site', None)
if site is not None:
# Add non-grouped site VLANs
site_vlans = VLAN.objects.filter(site=site, group=None)
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
# Add grouped site VLANs
for group in VLANGroup.objects.filter(site=site):
site_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append((
'{} / {}'.format(group.site.name, group.name),
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
self.fields['untagged_vlan'].choices = vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Interface.objects.all(),
widget=forms.MultipleHiddenInput()
@ -2389,10 +2522,28 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
required=False,
widget=StaticSelect2()
)
untagged_vlan = forms.ModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelect(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
tagged_vlans = forms.ModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
widget=APISelectMultiple(
api_url="/api/ipam/vlans/",
display_field='display_name',
full=True
)
)
class Meta:
nullable_fields = [
'lag', 'mac_address', 'mtu', 'description', 'mode',
'lag', 'mac_address', 'mtu', 'description', 'mode', 'untagged_vlan', 'tagged_vlans'
]
def __init__(self, *args, **kwargs):
@ -2408,6 +2559,36 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
else:
self.fields['lag'].choices = []
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
vlan_choices = []
global_vlans = VLAN.objects.filter(site=None, group=None)
vlan_choices.append(
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
)
for group in VLANGroup.objects.filter(site=None):
global_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append(
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
)
if self.parent_obj is not None:
site = getattr(self.parent_obj, 'site', None)
if site is not None:
# Add non-grouped site VLANs
site_vlans = VLAN.objects.filter(site=site, group=None)
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
# Add grouped site VLANs
for group in VLANGroup.objects.filter(site=site):
site_group_vlans = VLAN.objects.filter(group=group)
vlan_choices.append((
'{} / {}'.format(group.site.name, group.name),
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
self.fields['untagged_vlan'].choices = vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
class InterfaceBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(