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

Add in in-line vlan editing and Bulk vlan editing (#3350)

* Fixes #3341 - Added in-line vlan editing
* Fixes #2160 - Added bulk vlan editing

Inconsequential behaviour changes:

* APISelect can now take "full=True" to return a non-brief set
* Select2 will no group by "group & site, group, site, global" if full=True is set in APISelect
This commit is contained in:
Daniel Sheppard
2019-09-06 12:45:37 -05:00
committed by GitHub
parent 8f5e73a598
commit 9c6dbd7337
7 changed files with 155 additions and 157 deletions

View File

@@ -56,6 +56,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.
@@ -2110,7 +2129,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
)
@@ -2149,112 +2187,8 @@ 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
vlan_choices = []
# Add non-grouped global VLANs
global_vlans = VLAN.objects.filter(site=None, group=None).exclude(pk__in=assigned_vlans)
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)
vlan_choices.append(
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
)
site = getattr(self.instance.parent, '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)
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)
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)
class InterfaceCreateForm(ComponentForm, forms.Form):
class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
name_pattern = ExpandableNameField(
label='Name'
)
@@ -2298,6 +2232,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):
@@ -2316,7 +2268,7 @@ class InterfaceCreateForm(ComponentForm, forms.Form):
self.fields['lag'].queryset = Interface.objects.none()
class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Interface.objects.all(),
widget=forms.MultipleHiddenInput()
@@ -2360,10 +2312,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):