Coverage for virtualization/forms/bulk_import.py: 76%
85 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.utils.safestring import mark_safe
2from django.utils.translation import gettext_lazy as _
4from dcim.choices import InterfaceModeChoices
5from dcim.forms.mixins import ScopedImportForm
6from dcim.models import Device, DeviceRole, Platform, Site
7from extras.models import ConfigTemplate
8from ipam.choices import VLANQinQRoleChoices
9from ipam.models import VLAN, VRF, VLANGroup
10from netbox.forms import NetBoxModelImportForm, OrganizationalModelImportForm, OwnerCSVMixin, PrimaryModelImportForm
11from tenancy.models import Tenant
12from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
14from ..choices import *
15from ..models import *
17__all__ = (
18 'ClusterGroupImportForm',
19 'ClusterImportForm',
20 'ClusterTypeImportForm',
21 'VMInterfaceImportForm',
22 'VirtualDiskImportForm',
23 'VirtualMachineImportForm',
24 'VirtualMachineTypeImportForm',
25)
28class ClusterTypeImportForm(OrganizationalModelImportForm):
30 class Meta:
31 model = ClusterType
32 fields = ('name', 'slug', 'description', 'owner', 'comments', 'tags')
35class ClusterGroupImportForm(OrganizationalModelImportForm):
37 class Meta:
38 model = ClusterGroup
39 fields = ('name', 'slug', 'description', 'owner', 'comments', 'tags')
42class ClusterImportForm(ScopedImportForm, PrimaryModelImportForm):
43 type = CSVModelChoiceField(
44 label=_('Type'),
45 queryset=ClusterType.objects.all(),
46 to_field_name='name',
47 help_text=_('Type of cluster')
48 )
49 group = CSVModelChoiceField(
50 label=_('Group'),
51 queryset=ClusterGroup.objects.all(),
52 to_field_name='name',
53 required=False,
54 help_text=_('Assigned cluster group')
55 )
56 status = CSVChoiceField(
57 label=_('Status'),
58 choices=ClusterStatusChoices,
59 help_text=_('Operational status')
60 )
61 site = CSVModelChoiceField(
62 label=_('Site'),
63 queryset=Site.objects.all(),
64 to_field_name='name',
65 required=False,
66 help_text=_('Assigned site')
67 )
68 tenant = CSVModelChoiceField(
69 label=_('Tenant'),
70 queryset=Tenant.objects.all(),
71 to_field_name='name',
72 required=False,
73 help_text=_('Assigned tenant')
74 )
76 class Meta:
77 model = Cluster
78 fields = (
79 'name', 'type', 'group', 'status', 'scope_type', 'scope_name', 'scope_id', 'tenant', 'description', 'owner',
80 'comments', 'tags',
81 )
82 labels = {
83 'scope_id': _('Scope ID'),
84 }
87class VirtualMachineTypeImportForm(PrimaryModelImportForm):
88 default_platform = CSVModelChoiceField(
89 label=_('Default platform'),
90 queryset=Platform.objects.all(),
91 required=False,
92 to_field_name='name',
93 help_text=_('Assigned default platform'),
94 )
96 class Meta:
97 model = VirtualMachineType
98 fields = (
99 'name', 'slug', 'default_platform', 'default_vcpus', 'default_memory', 'description',
100 'owner', 'comments', 'tags',
101 )
104class VirtualMachineImportForm(PrimaryModelImportForm):
105 virtual_machine_type = CSVModelChoiceField(
106 label=_('Virtual machine type'),
107 queryset=VirtualMachineType.objects.all(),
108 to_field_name='name',
109 required=False,
110 help_text=_('Optional virtual machine type'),
111 )
112 status = CSVChoiceField(
113 label=_('Status'),
114 choices=VirtualMachineStatusChoices,
115 help_text=_('Operational status')
116 )
117 start_on_boot = CSVChoiceField(
118 label=_('Start on boot'),
119 choices=VirtualMachineStartOnBootChoices,
120 help_text=_('Start on boot in hypervisor'),
121 required=False,
122 )
123 site = CSVModelChoiceField(
124 label=_('Site'),
125 queryset=Site.objects.all(),
126 to_field_name='name',
127 required=False,
128 help_text=_('Assigned site (inferred from cluster or device if omitted)')
129 )
130 cluster = CSVModelChoiceField(
131 label=_('Cluster'),
132 queryset=Cluster.objects.all(),
133 to_field_name='name',
134 required=False,
135 help_text=_('Assigned cluster (required when the device belongs to a cluster)')
136 )
137 device = CSVModelChoiceField(
138 label=_('Device'),
139 queryset=Device.objects.all(),
140 to_field_name='name',
141 required=False,
142 help_text=_('Host device (standalone or within a cluster)')
143 )
144 role = CSVModelChoiceField(
145 label=_('Role'),
146 queryset=DeviceRole.objects.filter(
147 vm_role=True
148 ),
149 required=False,
150 to_field_name='name',
151 help_text=_('Functional role')
152 )
153 tenant = CSVModelChoiceField(
154 label=_('Tenant'),
155 queryset=Tenant.objects.all(),
156 required=False,
157 to_field_name='name',
158 help_text=_('Assigned tenant')
159 )
160 platform = CSVModelChoiceField(
161 label=_('Platform'),
162 queryset=Platform.objects.all(),
163 required=False,
164 to_field_name='name',
165 help_text=_('Assigned platform')
166 )
167 config_template = CSVModelChoiceField(
168 queryset=ConfigTemplate.objects.all(),
169 to_field_name='name',
170 required=False,
171 label=_('Config template'),
172 help_text=_('Config template')
173 )
175 class Meta:
176 model = VirtualMachine
177 fields = (
178 'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot', 'site', 'cluster', 'device',
179 'platform', 'vcpus', 'memory', 'disk', 'description', 'serial',
180 'tenant', 'owner', 'comments', 'tags', 'config_template',
181 )
184class VMInterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
185 virtual_machine = CSVModelChoiceField(
186 label=_('Virtual machine'),
187 queryset=VirtualMachine.objects.all(),
188 to_field_name='name'
189 )
190 parent = CSVModelChoiceField(
191 label=_('Parent'),
192 queryset=VMInterface.objects.all(),
193 required=False,
194 to_field_name='name',
195 help_text=_('Parent interface'),
196 )
197 bridge = CSVModelChoiceField(
198 label=_('Bridge'),
199 queryset=VMInterface.objects.all(),
200 required=False,
201 to_field_name='name',
202 help_text=_('Bridged interface'),
203 )
204 mode = CSVChoiceField(
205 label=_('Mode'),
206 choices=InterfaceModeChoices,
207 required=False,
208 help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)'),
209 )
210 vlan_group = CSVModelChoiceField(
211 label=_('VLAN group'),
212 queryset=VLANGroup.objects.all(),
213 required=False,
214 to_field_name='name',
215 help_text=_('Filter VLANs available for assignment by group'),
216 )
217 untagged_vlan = CSVModelChoiceField(
218 label=_('Untagged VLAN'),
219 queryset=VLAN.objects.all(),
220 required=False,
221 to_field_name='vid',
222 help_text=_('Assigned untagged VLAN ID (filtered by VLAN group)'),
223 )
224 tagged_vlans = CSVModelMultipleChoiceField(
225 label=_('Tagged VLANs'),
226 queryset=VLAN.objects.all(),
227 required=False,
228 to_field_name='vid',
229 help_text=mark_safe(
230 _(
231 'Assigned tagged VLAN IDs separated by commas, encased with double quotes '
232 '(filtered by VLAN group). Example:'
233 )
234 + ' <code>"100,200,300"</code>'
235 ),
236 )
237 qinq_svlan = CSVModelChoiceField(
238 label=_('Q-in-Q Service VLAN'),
239 queryset=VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
240 required=False,
241 to_field_name='vid',
242 help_text=_('Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)'),
243 )
244 vrf = CSVModelChoiceField(
245 label=_('VRF'),
246 queryset=VRF.objects.all(),
247 required=False,
248 to_field_name='rd',
249 help_text=_('Assigned VRF')
250 )
252 class Meta:
253 model = VMInterface
254 fields = (
255 'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode', 'vlan_group',
256 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'owner', 'tags',
257 )
259 def __init__(self, data=None, *args, **kwargs):
260 super().__init__(data, *args, **kwargs)
262 if data:
263 # Limit interface choices for parent & bridge interfaces to the assigned VM
264 if virtual_machine := data.get('virtual_machine'):
265 params = {
266 f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": virtual_machine
267 }
268 self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
269 self.fields['bridge'].queryset = self.fields['bridge'].queryset.filter(**params)
271 # Limit choices for VLANs to the assigned VLAN group
272 if vlan_group := data.get('vlan_group'):
273 params = {f"group__{self.fields['vlan_group'].to_field_name}": vlan_group}
274 self.fields['untagged_vlan'].queryset = self.fields['untagged_vlan'].queryset.filter(**params)
275 self.fields['tagged_vlans'].queryset = self.fields['tagged_vlans'].queryset.filter(**params)
276 self.fields['qinq_svlan'].queryset = self.fields['qinq_svlan'].queryset.filter(**params)
278 def clean_enabled(self):
279 # Make sure enabled is True when it's not included in the uploaded data
280 if 'enabled' not in self.data:
281 return True
282 return self.cleaned_data['enabled']
285class VirtualDiskImportForm(OwnerCSVMixin, NetBoxModelImportForm):
286 virtual_machine = CSVModelChoiceField(
287 label=_('Virtual machine'),
288 queryset=VirtualMachine.objects.all(),
289 to_field_name='name'
290 )
292 class Meta:
293 model = VirtualDisk
294 fields = (
295 'virtual_machine', 'name', 'size', 'description', 'owner', 'tags'
296 )