diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index ab17831a1..6795726a6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -14,11 +14,10 @@ from ipam.models import IPAddress, VLAN, VLANGroup from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, - BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, - ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, - FilterChoiceField, FilterTreeNodeMultipleChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, - SelectWithPK, SmallTextarea, SlugField, + AnnotatedMultipleChoiceField, APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, + BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm, + ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField, + FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField, ) from virtualization.models import Cluster from .constants import ( @@ -37,6 +36,12 @@ from .models import ( DEVICE_BY_PK_RE = '{\d+\}' +INTERFACE_MODE_HELP_TEXT = """ +Access: One untagged VLAN
+Tagged: One untagged VLAN and/or one or more tagged VLANs
+Tagged All: Implies all VLANs are available (w/optional untagged VLAN) +""" + def get_device_by_name_or_pk(name): """ @@ -1657,7 +1662,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): class Meta: model = Interface fields = [ - 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description', + 'device', 'name', 'form_factor', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', ] widgets = { @@ -1667,9 +1672,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): 'mode': '802.1Q Mode', } help_texts = { - 'mode': "Access: One untagged VLAN
" - "Tagged: One untagged VLAN and/or one or more tagged VLANs
" - "Tagged All: Implies all VLANs are available (w/optional untagged VLAN)" + 'mode': INTERFACE_MODE_HELP_TEXT, } def __init__(self, *args, **kwargs): @@ -1732,17 +1735,37 @@ class InterfaceAssignVLANsForm(BootstrapMixin, forms.ModelForm): if self.instance.untagged_vlan is not None: assigned_vlans.append(self.instance.untagged_vlan.pk) - # Initialize VLAN choices - device = self.instance.device - vlan_choices = [ - ('Global', [(vlan.pk, vlan) for vlan in VLAN.objects.filter(site=None).exclude(pk__in=assigned_vlans)]), - (device.site.name, [(vlan.pk, vlan) for vlan in VLAN.objects.filter(site=device.site, group=None).exclude(pk__in=assigned_vlans)]), - ] - for group in VLANGroup.objects.filter(site=device.site): - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in VLAN.objects.filter(group=group).exclude(pk__in=assigned_vlans)] - )) + # Compile VLAN choices + vlan_choices = [] + + # Add 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]) + ) + + parent = self.instance.parent + if parent is not None: + + # Add site VLANs + site_vlans = VLAN.objects.filter(site=parent.site, group=None).exclude(pk__in=assigned_vlans) + vlan_choices.append((parent.site.name, [(vlan.pk, vlan) for vlan in site_vlans])) + + # Add grouped site VLANs + for group in VLANGroup.objects.filter(site=parent.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): diff --git a/netbox/templates/dcim/inc/interface_vlans_table.html b/netbox/templates/dcim/inc/interface_vlans_table.html new file mode 100644 index 000000000..863921b0d --- /dev/null +++ b/netbox/templates/dcim/inc/interface_vlans_table.html @@ -0,0 +1,55 @@ + + + + + + + + {% with tagged_vlans=obj.tagged_vlans.all %} + {% if obj.untagged_vlan and obj.untagged_vlan not in tagged_vlans %} + + + + + + + {% endif %} + {% for vlan in tagged_vlans %} + + + + + + + {% endfor %} + {% if not obj.untagged_vlan and not tagged_vlans %} + + + + {% else %} + + + + + + {% endif %} + {% endwith %} +
VIDNameUntaggedTagged
+ {{ obj.untagged_vlan.vid }} + {{ obj.untagged_vlan.name }} + + + +
+ {{ vlan.vid }} + {{ vlan.name }} + + + +
+ No VLANs assigned +
+ Clear + + Clear All +
diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index cef4a772b..0e212cf3e 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -19,59 +19,7 @@ {% if obj.mode %}
802.1Q VLANs
- - - - - - - - {% if obj.untagged_vlan %} - - - - - - - {% endif %} - {% for vlan in obj.tagged_vlans.all %} - - - - - - - {% endfor %} - {% if not obj.untagged_vlan and not obj.tagged_vlans.exists %} - - - - {% else %} - - - - - - {% endif %} -
VIDNameUntaggedTagged
- {{ obj.untagged_vlan.vid }} - {{ obj.untagged_vlan.name }} - - - -
- {{ vlan.vid }} - {{ vlan.name }} - - - -
- No VLANs assigned -
- Clear - - Clear All -
+ {% include 'dcim/inc/interface_vlans_table.html' %}