mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-23 20:12:42 -06:00
Closes #14171: Add VLAN-related fields to import forms (#20730)
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
This commit is contained in:
@@ -9,7 +9,8 @@ from dcim.choices import *
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import VRF, IPAddress
|
from ipam.choices import VLANQinQRoleChoices
|
||||||
|
from ipam.models import VLAN, VRF, IPAddress, VLANGroup
|
||||||
from netbox.choices import *
|
from netbox.choices import *
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@@ -17,7 +18,7 @@ from utilities.forms.fields import (
|
|||||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
|
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
|
||||||
SlugField,
|
SlugField,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, VMInterface, VirtualMachine
|
from virtualization.models import Cluster, VirtualMachine, VMInterface
|
||||||
from wireless.choices import WirelessRoleChoices
|
from wireless.choices import WirelessRoleChoices
|
||||||
from .common import ModuleCommonForm
|
from .common import ModuleCommonForm
|
||||||
|
|
||||||
@@ -938,7 +939,7 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=mark_safe(
|
help_text=mark_safe(
|
||||||
_('VDC names separated by commas, encased with double quotes. Example:') + ' <code>vdc1,vdc2,vdc3</code>'
|
_('VDC names separated by commas, encased with double quotes. Example:') + ' <code>"vdc1,vdc2,vdc3"</code>'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
@@ -967,7 +968,41 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
label=_('Mode'),
|
label=_('Mode'),
|
||||||
choices=InterfaceModeChoices,
|
choices=InterfaceModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
|
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)'),
|
||||||
|
)
|
||||||
|
vlan_group = CSVModelChoiceField(
|
||||||
|
label=_('VLAN group'),
|
||||||
|
queryset=VLANGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Filter VLANs available for assignment by group'),
|
||||||
|
)
|
||||||
|
untagged_vlan = CSVModelChoiceField(
|
||||||
|
label=_('Untagged VLAN'),
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=_('Assigned untagged VLAN ID (filtered by VLAN group)'),
|
||||||
|
)
|
||||||
|
tagged_vlans = CSVModelMultipleChoiceField(
|
||||||
|
label=_('Tagged VLANs'),
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=mark_safe(
|
||||||
|
_(
|
||||||
|
'Assigned tagged VLAN IDs separated by commas, encased with double quotes '
|
||||||
|
'(filtered by VLAN group). Example:'
|
||||||
|
)
|
||||||
|
+ ' <code>"100,200,300"</code>'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
qinq_svlan = CSVModelChoiceField(
|
||||||
|
label=_('Q-in-Q Service VLAN'),
|
||||||
|
queryset=VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=_('Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)'),
|
||||||
)
|
)
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
@@ -988,7 +1023,8 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
|
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
|
||||||
'mark_connected', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
|
'mark_connected', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
|
||||||
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
|
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'rf_role', 'rf_channel',
|
||||||
|
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@@ -1005,6 +1041,13 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
self.fields['lag'].queryset = self.fields['lag'].queryset.filter(**params)
|
self.fields['lag'].queryset = self.fields['lag'].queryset.filter(**params)
|
||||||
self.fields['vdcs'].queryset = self.fields['vdcs'].queryset.filter(**params)
|
self.fields['vdcs'].queryset = self.fields['vdcs'].queryset.filter(**params)
|
||||||
|
|
||||||
|
# Limit choices for VLANs to the assigned VLAN group
|
||||||
|
if vlan_group := data.get('vlan_group'):
|
||||||
|
params = {f"group__{self.fields['vlan_group'].to_field_name}": vlan_group}
|
||||||
|
self.fields['untagged_vlan'].queryset = self.fields['untagged_vlan'].queryset.filter(**params)
|
||||||
|
self.fields['tagged_vlans'].queryset = self.fields['tagged_vlans'].queryset.filter(**params)
|
||||||
|
self.fields['qinq_svlan'].queryset = self.fields['qinq_svlan'].queryset.filter(**params)
|
||||||
|
|
||||||
def clean_enabled(self):
|
def clean_enabled(self):
|
||||||
# Make sure enabled is True when it's not included in the uploaded data
|
# Make sure enabled is True when it's not included in the uploaded data
|
||||||
if 'enabled' not in self.data:
|
if 'enabled' not in self.data:
|
||||||
|
|||||||
@@ -2834,10 +2834,19 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"device,name,type,vrf.pk,poe_mode,poe_type",
|
"device,name,type,vrf.pk,poe_mode,poe_type,mode,untagged_vlan,tagged_vlans",
|
||||||
f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
|
(
|
||||||
f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
|
f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
|
||||||
f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
|
||||||
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
|
||||||
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
from dcim.forms.mixins import ScopedImportForm
|
from dcim.forms.mixins import ScopedImportForm
|
||||||
from dcim.models import Device, DeviceRole, Platform, Site
|
from dcim.models import Device, DeviceRole, Platform, Site
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import VRF
|
from ipam.choices import VLANQinQRoleChoices
|
||||||
|
from ipam.models import VLAN, VRF, VLANGroup
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
from utilities.forms.fields import (
|
||||||
|
CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField,
|
||||||
|
SlugField,
|
||||||
|
)
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.models import *
|
from virtualization.models import *
|
||||||
|
|
||||||
@@ -158,20 +163,54 @@ class VMInterfaceImportForm(NetBoxModelImportForm):
|
|||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Parent interface')
|
help_text=_('Parent interface'),
|
||||||
)
|
)
|
||||||
bridge = CSVModelChoiceField(
|
bridge = CSVModelChoiceField(
|
||||||
label=_('Bridge'),
|
label=_('Bridge'),
|
||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Bridged interface')
|
help_text=_('Bridged interface'),
|
||||||
)
|
)
|
||||||
mode = CSVChoiceField(
|
mode = CSVChoiceField(
|
||||||
label=_('Mode'),
|
label=_('Mode'),
|
||||||
choices=InterfaceModeChoices,
|
choices=InterfaceModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
|
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)'),
|
||||||
|
)
|
||||||
|
vlan_group = CSVModelChoiceField(
|
||||||
|
label=_('VLAN group'),
|
||||||
|
queryset=VLANGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Filter VLANs available for assignment by group'),
|
||||||
|
)
|
||||||
|
untagged_vlan = CSVModelChoiceField(
|
||||||
|
label=_('Untagged VLAN'),
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=_('Assigned untagged VLAN ID (filtered by VLAN group)'),
|
||||||
|
)
|
||||||
|
tagged_vlans = CSVModelMultipleChoiceField(
|
||||||
|
label=_('Tagged VLANs'),
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=mark_safe(
|
||||||
|
_(
|
||||||
|
'Assigned tagged VLAN IDs separated by commas, encased with double quotes '
|
||||||
|
'(filtered by VLAN group). Example:'
|
||||||
|
)
|
||||||
|
+ ' <code>"100,200,300"</code>'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
qinq_svlan = CSVModelChoiceField(
|
||||||
|
label=_('Q-in-Q Service VLAN'),
|
||||||
|
queryset=VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
|
||||||
|
required=False,
|
||||||
|
to_field_name='vid',
|
||||||
|
help_text=_('Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)'),
|
||||||
)
|
)
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
@@ -185,7 +224,7 @@ class VMInterfaceImportForm(NetBoxModelImportForm):
|
|||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode',
|
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode',
|
||||||
'vrf', 'tags'
|
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@@ -200,6 +239,13 @@ class VMInterfaceImportForm(NetBoxModelImportForm):
|
|||||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
|
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
|
||||||
self.fields['bridge'].queryset = self.fields['bridge'].queryset.filter(**params)
|
self.fields['bridge'].queryset = self.fields['bridge'].queryset.filter(**params)
|
||||||
|
|
||||||
|
# Limit choices for VLANs to the assigned VLAN group
|
||||||
|
if vlan_group := data.get('vlan_group'):
|
||||||
|
params = {f"group__{self.fields['vlan_group'].to_field_name}": vlan_group}
|
||||||
|
self.fields['untagged_vlan'].queryset = self.fields['untagged_vlan'].queryset.filter(**params)
|
||||||
|
self.fields['tagged_vlans'].queryset = self.fields['tagged_vlans'].queryset.filter(**params)
|
||||||
|
self.fields['qinq_svlan'].queryset = self.fields['qinq_svlan'].queryset.filter(**params)
|
||||||
|
|
||||||
def clean_enabled(self):
|
def clean_enabled(self):
|
||||||
# Make sure enabled is True when it's not included in the uploaded data
|
# Make sure enabled is True when it's not included in the uploaded data
|
||||||
if 'enabled' not in self.data:
|
if 'enabled' not in self.data:
|
||||||
|
|||||||
@@ -395,10 +395,19 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"virtual_machine,name,vrf.pk",
|
"virtual_machine,name,vrf.pk,mode,untagged_vlan,tagged_vlans",
|
||||||
f"Virtual Machine 2,Interface 4,{vrfs[0].pk}",
|
(
|
||||||
f"Virtual Machine 2,Interface 5,{vrfs[0].pk}",
|
f"Virtual Machine 2,Interface 4,{vrfs[0].pk},"
|
||||||
f"Virtual Machine 2,Interface 6,{vrfs[0].pk}",
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
f"Virtual Machine 2,Interface 5,{vrfs[0].pk},"
|
||||||
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
f"Virtual Machine 2,Interface 6,{vrfs[0].pk},"
|
||||||
|
f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
|||||||
Reference in New Issue
Block a user