Coverage for dcim/forms/bulk_edit.py: 83%
563 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 decimal import Decimal
3from django import forms
4from django.conf import settings
5from django.utils.translation import gettext_lazy as _
6from timezone_field import TimeZoneFormField
8from dcim.choices import *
9from dcim.constants import *
10from dcim.models import *
11from extras.models import ConfigTemplate
12from ipam.choices import VLANQinQRoleChoices
13from ipam.models import ASN, VLAN, VRF, VLANGroup
14from netbox.choices import *
15from netbox.forms import (
16 NestedGroupModelBulkEditForm,
17 NetBoxModelBulkEditForm,
18 OrganizationalModelBulkEditForm,
19 PrimaryModelBulkEditForm,
20)
21from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
22from tenancy.models import Tenant
23from users.models import User
24from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
25from utilities.forms.fields import (
26 ChoiceField,
27 ColorField,
28 DynamicModelChoiceField,
29 DynamicModelMultipleChoiceField,
30 JSONField,
31 PositiveBigIntegerField,
32)
33from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
34from utilities.forms.widgets import BulkEditNullBooleanSelect, DatePicker, NumberWithOptions
35from virtualization.models import Cluster
36from wireless.choices import WirelessRoleChoices
37from wireless.models import WirelessLAN, WirelessLANGroup
39__all__ = (
40 'CableBulkEditForm',
41 'CableBundleBulkEditForm',
42 'ConsolePortBulkEditForm',
43 'ConsolePortTemplateBulkEditForm',
44 'ConsoleServerPortBulkEditForm',
45 'ConsoleServerPortTemplateBulkEditForm',
46 'CoolingFeedBulkEditForm',
47 'CoolingIntakeBulkEditForm',
48 'CoolingIntakeTemplateBulkEditForm',
49 'CoolingOutflowBulkEditForm',
50 'CoolingOutflowTemplateBulkEditForm',
51 'CoolingSourceBulkEditForm',
52 'DeviceBayBulkEditForm',
53 'DeviceBayTemplateBulkEditForm',
54 'DeviceBulkEditForm',
55 'DeviceRoleBulkEditForm',
56 'DeviceTypeBulkEditForm',
57 'FrontPortBulkEditForm',
58 'FrontPortTemplateBulkEditForm',
59 'InterfaceBulkEditForm',
60 'InterfaceTemplateBulkEditForm',
61 'InventoryItemBulkEditForm',
62 'InventoryItemRoleBulkEditForm',
63 'InventoryItemTemplateBulkEditForm',
64 'LocationBulkEditForm',
65 'MACAddressBulkEditForm',
66 'ManufacturerBulkEditForm',
67 'ModuleBayBulkEditForm',
68 'ModuleBayTemplateBulkEditForm',
69 'ModuleBayTypeBulkEditForm',
70 'ModuleBulkEditForm',
71 'ModuleTypeBulkEditForm',
72 'ModuleTypeProfileBulkEditForm',
73 'PlatformBulkEditForm',
74 'PowerFeedBulkEditForm',
75 'PowerOutletBulkEditForm',
76 'PowerOutletTemplateBulkEditForm',
77 'PowerPanelBulkEditForm',
78 'PowerPortBulkEditForm',
79 'PowerPortTemplateBulkEditForm',
80 'RackBulkEditForm',
81 'RackGroupBulkEditForm',
82 'RackReservationBulkEditForm',
83 'RackRoleBulkEditForm',
84 'RackTypeBulkEditForm',
85 'RearPortBulkEditForm',
86 'RearPortTemplateBulkEditForm',
87 'RegionBulkEditForm',
88 'SiteBulkEditForm',
89 'SiteGroupBulkEditForm',
90 'VirtualChassisBulkEditForm',
91 'VirtualDeviceContextBulkEditForm'
92)
95class RegionBulkEditForm(NestedGroupModelBulkEditForm):
96 parent = DynamicModelChoiceField(
97 label=_('Parent'),
98 queryset=Region.objects.all(),
99 required=False
100 )
102 model = Region
103 fieldsets = (
104 FieldSet('parent', 'description'),
105 )
106 nullable_fields = ('parent', 'description', 'comments')
109class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm):
110 parent = DynamicModelChoiceField(
111 label=_('Parent'),
112 queryset=SiteGroup.objects.all(),
113 required=False
114 )
116 model = SiteGroup
117 fieldsets = (
118 FieldSet('parent', 'description'),
119 )
120 nullable_fields = ('parent', 'description', 'comments')
123class SiteBulkEditForm(PrimaryModelBulkEditForm):
124 status = ChoiceField(
125 label=_('Status'),
126 choices=add_blank_choice(SiteStatusChoices),
127 required=False,
128 initial=''
129 )
130 region = DynamicModelChoiceField(
131 label=_('Region'),
132 queryset=Region.objects.all(),
133 required=False
134 )
135 group = DynamicModelChoiceField(
136 label=_('Group'),
137 queryset=SiteGroup.objects.all(),
138 required=False
139 )
140 tenant = DynamicModelChoiceField(
141 label=_('Tenant'),
142 queryset=Tenant.objects.all(),
143 required=False
144 )
145 facility = forms.CharField(
146 label=_('Facility'),
147 max_length=50,
148 required=False
149 )
150 asns = DynamicModelMultipleChoiceField(
151 queryset=ASN.objects.all(),
152 label=_('ASNs'),
153 required=False
154 )
155 contact_name = forms.CharField(
156 label=_('Contact name'),
157 max_length=50,
158 required=False
159 )
160 contact_phone = forms.CharField(
161 label=_('Contact phone'),
162 max_length=20,
163 required=False
164 )
165 contact_email = forms.EmailField(
166 required=False,
167 label=_('Contact E-mail')
168 )
169 time_zone = TimeZoneFormField(
170 label=_('Time zone'),
171 choices=add_blank_choice(TimeZoneFormField().choices),
172 required=False
173 )
175 model = Site
176 fieldsets = (
177 FieldSet('status', 'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description'),
178 )
179 nullable_fields = (
180 'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description', 'comments',
181 )
184class LocationBulkEditForm(NestedGroupModelBulkEditForm):
185 site = DynamicModelChoiceField(
186 label=_('Site'),
187 queryset=Site.objects.all(),
188 required=False
189 )
190 parent = DynamicModelChoiceField(
191 label=_('Parent'),
192 queryset=Location.objects.all(),
193 required=False,
194 query_params={
195 'site_id': '$site'
196 }
197 )
198 status = ChoiceField(
199 label=_('Status'),
200 choices=add_blank_choice(LocationStatusChoices),
201 required=False,
202 initial=''
203 )
204 tenant = DynamicModelChoiceField(
205 label=_('Tenant'),
206 queryset=Tenant.objects.all(),
207 required=False
208 )
209 facility = forms.CharField(
210 label=_('Facility'),
211 max_length=50,
212 required=False
213 )
215 model = Location
216 fieldsets = (
217 FieldSet('site', 'parent', 'status', 'tenant', 'facility', 'description'),
218 )
219 nullable_fields = ('parent', 'tenant', 'facility', 'description', 'comments')
222class RackGroupBulkEditForm(OrganizationalModelBulkEditForm):
223 model = RackGroup
224 fieldsets = (
225 FieldSet('description'),
226 )
227 nullable_fields = ('description', 'comments')
230class RackRoleBulkEditForm(OrganizationalModelBulkEditForm):
231 color = ColorField(
232 label=_('Color'),
233 required=False
234 )
236 model = RackRole
237 fieldsets = (
238 FieldSet('color', 'description'),
239 )
240 nullable_fields = ('color', 'description', 'comments')
243class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
244 manufacturer = DynamicModelChoiceField(
245 label=_('Manufacturer'),
246 queryset=Manufacturer.objects.all(),
247 required=False
248 )
249 form_factor = ChoiceField(
250 label=_('Form factor'),
251 choices=add_blank_choice(RackFormFactorChoices),
252 required=False
253 )
254 width = ChoiceField(
255 label=_('Width'),
256 choices=add_blank_choice(RackWidthChoices),
257 required=False
258 )
259 u_height = forms.IntegerField(
260 required=False,
261 label=_('Height (U)')
262 )
263 starting_unit = forms.IntegerField(
264 required=False,
265 min_value=1
266 )
267 desc_units = forms.NullBooleanField(
268 required=False,
269 widget=BulkEditNullBooleanSelect,
270 label=_('Descending units')
271 )
272 outer_width = forms.IntegerField(
273 label=_('Outer width'),
274 required=False,
275 min_value=1
276 )
277 outer_height = forms.IntegerField(
278 label=_('Outer height'),
279 required=False,
280 min_value=1
281 )
282 outer_depth = forms.IntegerField(
283 label=_('Outer depth'),
284 required=False,
285 min_value=1
286 )
287 outer_unit = ChoiceField(
288 label=_('Outer unit'),
289 choices=add_blank_choice(RackDimensionUnitChoices),
290 required=False
291 )
292 mounting_depth = forms.IntegerField(
293 label=_('Mounting depth'),
294 required=False,
295 min_value=1
296 )
297 weight = forms.DecimalField(
298 label=_('Weight'),
299 min_value=0,
300 required=False
301 )
302 max_weight = forms.IntegerField(
303 label=_('Max weight'),
304 min_value=0,
305 required=False
306 )
307 weight_unit = ChoiceField(
308 label=_('Weight unit'),
309 choices=add_blank_choice(WeightUnitChoices),
310 required=False,
311 initial=''
312 )
313 cooling_capability = forms.ChoiceField(
314 label=_('Cooling capability'),
315 choices=add_blank_choice(RackCoolingCapabilityChoices),
316 required=False
317 )
318 cooling_capacity = forms.DecimalField(
319 label=_('Cooling capacity'),
320 min_value=0,
321 required=False
322 )
324 model = RackType
325 fieldsets = (
326 FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
327 FieldSet(
328 InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
329 InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
330 'mounting_depth',
331 name=_('Dimensions')
332 ),
333 FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
334 FieldSet('cooling_capability', 'cooling_capacity', name=_('Cooling')),
335 )
336 nullable_fields = (
337 'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
338 'max_weight', 'weight_unit', 'cooling_capability', 'cooling_capacity', 'description', 'comments',
339 )
342class RackBulkEditForm(PrimaryModelBulkEditForm):
343 region = DynamicModelChoiceField(
344 label=_('Region'),
345 queryset=Region.objects.all(),
346 required=False,
347 initial_params={
348 'sites': '$site'
349 }
350 )
351 site_group = DynamicModelChoiceField(
352 label=_('Site group'),
353 queryset=SiteGroup.objects.all(),
354 required=False,
355 initial_params={
356 'sites': '$site'
357 }
358 )
359 site = DynamicModelChoiceField(
360 label=_('Site'),
361 queryset=Site.objects.all(),
362 required=False,
363 query_params={
364 'region_id': '$region',
365 'group_id': '$site_group',
366 }
367 )
368 location = DynamicModelChoiceField(
369 label=_('Location'),
370 queryset=Location.objects.all(),
371 required=False,
372 query_params={
373 'site_id': '$site'
374 }
375 )
376 group = DynamicModelChoiceField(
377 label=_('Group'),
378 queryset=RackGroup.objects.all(),
379 required=False
380 )
381 tenant = DynamicModelChoiceField(
382 label=_('Tenant'),
383 queryset=Tenant.objects.all(),
384 required=False
385 )
386 status = ChoiceField(
387 label=_('Status'),
388 choices=add_blank_choice(RackStatusChoices),
389 required=False,
390 initial=''
391 )
392 role = DynamicModelChoiceField(
393 label=_('Role'),
394 queryset=RackRole.objects.all(),
395 required=False
396 )
397 rack_type = DynamicModelChoiceField(
398 label=_('Rack type'),
399 queryset=RackType.objects.all(),
400 required=False,
401 )
402 serial = forms.CharField(
403 max_length=50,
404 required=False,
405 label=_('Serial Number')
406 )
407 asset_tag = forms.CharField(
408 label=_('Asset tag'),
409 max_length=50,
410 required=False
411 )
412 form_factor = ChoiceField(
413 label=_('Form factor'),
414 choices=add_blank_choice(RackFormFactorChoices),
415 required=False
416 )
417 width = ChoiceField(
418 label=_('Width'),
419 choices=add_blank_choice(RackWidthChoices),
420 required=False
421 )
422 u_height = forms.IntegerField(
423 required=False,
424 label=_('Height (U)')
425 )
426 desc_units = forms.NullBooleanField(
427 required=False,
428 widget=BulkEditNullBooleanSelect,
429 label=_('Descending units')
430 )
431 outer_width = forms.IntegerField(
432 label=_('Outer width'),
433 required=False,
434 min_value=1
435 )
436 outer_height = forms.IntegerField(
437 label=_('Outer height'),
438 required=False,
439 min_value=1
440 )
441 outer_depth = forms.IntegerField(
442 label=_('Outer depth'),
443 required=False,
444 min_value=1
445 )
446 outer_unit = ChoiceField(
447 label=_('Outer unit'),
448 choices=add_blank_choice(RackDimensionUnitChoices),
449 required=False
450 )
451 mounting_depth = forms.IntegerField(
452 label=_('Mounting depth'),
453 required=False,
454 min_value=1
455 )
456 airflow = ChoiceField(
457 label=_('Airflow'),
458 choices=add_blank_choice(RackAirflowChoices),
459 required=False
460 )
461 cooling_capability = forms.ChoiceField(
462 label=_('Cooling capability'),
463 choices=add_blank_choice(RackCoolingCapabilityChoices),
464 required=False
465 )
466 cooling_capacity = forms.DecimalField(
467 label=_('Cooling capacity'),
468 min_value=0,
469 required=False
470 )
471 weight = forms.DecimalField(
472 label=_('Weight'),
473 min_value=0,
474 required=False
475 )
476 max_weight = forms.IntegerField(
477 label=_('Max weight'),
478 min_value=0,
479 required=False
480 )
481 weight_unit = ChoiceField(
482 label=_('Weight unit'),
483 choices=add_blank_choice(WeightUnitChoices),
484 required=False,
485 initial=''
486 )
488 model = Rack
489 fieldsets = (
490 FieldSet(
491 'status', 'group', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')
492 ),
493 FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
494 FieldSet('outer_width', 'outer_height', 'outer_depth', 'outer_unit', name=_('Outer Dimensions')),
495 FieldSet('form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'mounting_depth', name=_('Hardware')),
496 FieldSet('cooling_capability', 'cooling_capacity', name=_('Cooling')),
497 FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
498 )
499 nullable_fields = (
500 'location', 'group', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_height', 'outer_depth',
501 'outer_unit', 'cooling_capability', 'cooling_capacity', 'weight', 'max_weight', 'weight_unit', 'description',
502 'comments',
503 )
506class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
507 status = ChoiceField(
508 label=_('Status'),
509 choices=add_blank_choice(RackReservationStatusChoices),
510 required=False,
511 initial=''
512 )
513 user = forms.ModelChoiceField(
514 label=_('User'),
515 queryset=User.objects.order_by('username'),
516 required=False
517 )
518 tenant = DynamicModelChoiceField(
519 label=_('Tenant'),
520 queryset=Tenant.objects.all(),
521 required=False
522 )
524 model = RackReservation
525 fieldsets = (
526 FieldSet('status', 'user', 'tenant', 'description'),
527 )
528 nullable_fields = ('comments',)
531class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm):
532 model = Manufacturer
533 fieldsets = (
534 FieldSet('description'),
535 )
536 nullable_fields = ('description', 'comments')
539class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
540 manufacturer = DynamicModelChoiceField(
541 label=_('Manufacturer'),
542 queryset=Manufacturer.objects.all(),
543 required=False
544 )
545 default_platform = DynamicModelChoiceField(
546 label=_('Default platform'),
547 queryset=Platform.objects.all(),
548 required=False
549 )
550 part_number = forms.CharField(
551 label=_('Part number'),
552 required=False
553 )
554 u_height = forms.IntegerField(
555 label=_('U height'),
556 min_value=0,
557 required=False
558 )
559 is_full_depth = forms.NullBooleanField(
560 required=False,
561 widget=BulkEditNullBooleanSelect(),
562 label=_('Is full depth')
563 )
564 exclude_from_utilization = forms.NullBooleanField(
565 required=False,
566 widget=BulkEditNullBooleanSelect(),
567 label=_('Exclude from utilization')
568 )
569 airflow = ChoiceField(
570 label=_('Airflow'),
571 choices=add_blank_choice(DeviceAirflowChoices),
572 required=False
573 )
574 cooling_method = forms.ChoiceField(
575 label=_('Cooling method'),
576 choices=add_blank_choice(CoolingMethodChoices),
577 required=False
578 )
579 weight = forms.DecimalField(
580 label=_('Weight'),
581 min_value=0,
582 required=False
583 )
584 weight_unit = ChoiceField(
585 label=_('Weight unit'),
586 choices=add_blank_choice(WeightUnitChoices),
587 required=False,
588 initial=''
589 )
590 end_of_life = forms.DateField(
591 label=_('End of life'),
592 required=False,
593 widget=DatePicker()
594 )
596 model = DeviceType
597 fieldsets = (
598 FieldSet(
599 'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
600 'airflow', 'cooling_method', 'description', name=_('Device Type')
601 ),
602 FieldSet('weight', 'weight_unit', name=_('Weight')),
603 FieldSet('end_of_life', name=_('Lifecycle')),
604 )
605 nullable_fields = (
606 'part_number', 'airflow', 'cooling_method', 'weight', 'weight_unit', 'end_of_life', 'description', 'comments',
607 )
610class ModuleBayTypeBulkEditForm(PrimaryModelBulkEditForm):
611 manufacturer = DynamicModelChoiceField(
612 label=_('Manufacturer'),
613 queryset=Manufacturer.objects.all(),
614 required=False,
615 )
616 color = ColorField(
617 label=_('Color'),
618 required=False,
619 )
621 model = ModuleBayType
622 fieldsets = (
623 FieldSet('manufacturer', 'color', 'description', name=_('Module Bay Type')),
624 )
625 nullable_fields = ('manufacturer', 'color', 'description', 'comments')
628class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm):
629 schema = JSONField(
630 label=_('Schema'),
631 required=False
632 )
634 model = ModuleTypeProfile
635 fieldsets = (
636 FieldSet('name', 'description', 'schema', name=_('Profile')),
637 )
638 nullable_fields = ('description', 'comments')
641class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
642 profile = DynamicModelChoiceField(
643 label=_('Profile'),
644 queryset=ModuleTypeProfile.objects.all(),
645 required=False
646 )
647 manufacturer = DynamicModelChoiceField(
648 label=_('Manufacturer'),
649 queryset=Manufacturer.objects.all(),
650 required=False
651 )
652 part_number = forms.CharField(
653 label=_('Part number'),
654 required=False
655 )
656 airflow = ChoiceField(
657 label=_('Airflow'),
658 choices=add_blank_choice(ModuleAirflowChoices),
659 required=False
660 )
661 cooling_method = forms.ChoiceField(
662 label=_('Cooling method'),
663 choices=add_blank_choice(CoolingMethodChoices),
664 required=False
665 )
666 weight = forms.DecimalField(
667 label=_('Weight'),
668 min_value=0,
669 required=False
670 )
671 weight_unit = ChoiceField(
672 label=_('Weight unit'),
673 choices=add_blank_choice(WeightUnitChoices),
674 required=False,
675 initial=''
676 )
677 end_of_life = forms.DateField(
678 label=_('End of life'),
679 required=False,
680 widget=DatePicker()
681 )
683 add_module_bay_types = DynamicModelMultipleChoiceField(
684 label=_('Add bay types'),
685 queryset=ModuleBayType.objects.all(),
686 required=False,
687 )
688 remove_module_bay_types = DynamicModelMultipleChoiceField(
689 label=_('Remove bay types'),
690 queryset=ModuleBayType.objects.all(),
691 required=False,
692 )
694 model = ModuleType
695 fieldsets = (
696 FieldSet('profile', 'manufacturer', 'part_number', 'description', name=_('Module Type')),
697 FieldSet(
698 InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
699 name=_('Chassis')
700 ),
701 FieldSet('cooling_method', 'airflow', name=_('Cooling')),
702 FieldSet('add_module_bay_types', 'remove_module_bay_types', name=_('Bay Types')),
703 FieldSet('end_of_life', name=_('Lifecycle')),
704 )
705 nullable_fields = (
706 'part_number', 'weight', 'weight_unit', 'profile', 'end_of_life', 'description', 'comments', 'cooling_method',
707 )
710class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
711 parent = DynamicModelChoiceField(
712 label=_('Parent'),
713 queryset=DeviceRole.objects.all(),
714 required=False,
715 )
716 color = ColorField(
717 label=_('Color'),
718 required=False
719 )
720 vm_role = forms.NullBooleanField(
721 required=False,
722 widget=BulkEditNullBooleanSelect,
723 label=_('VM role')
724 )
725 config_template = DynamicModelChoiceField(
726 label=_('Config template'),
727 queryset=ConfigTemplate.objects.all(),
728 required=False
729 )
731 model = DeviceRole
732 fieldsets = (
733 FieldSet('parent', 'color', 'vm_role', 'config_template', 'description'),
734 )
735 nullable_fields = ('parent', 'color', 'config_template', 'description', 'comments')
738class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
739 parent = DynamicModelChoiceField(
740 label=_('Parent'),
741 queryset=Platform.objects.all(),
742 required=False,
743 )
744 manufacturer = DynamicModelChoiceField(
745 label=_('Manufacturer'),
746 queryset=Manufacturer.objects.all(),
747 required=False
748 )
749 config_template = DynamicModelChoiceField(
750 label=_('Config template'),
751 queryset=ConfigTemplate.objects.all(),
752 required=False
753 )
755 model = Platform
756 fieldsets = (
757 FieldSet('parent', 'manufacturer', 'config_template', 'description'),
758 )
759 nullable_fields = ('parent', 'manufacturer', 'config_template', 'description', 'comments')
762class DeviceBulkEditForm(PrimaryModelBulkEditForm):
763 manufacturer = DynamicModelChoiceField(
764 label=_('Manufacturer'),
765 queryset=Manufacturer.objects.all(),
766 required=False
767 )
768 device_type = DynamicModelChoiceField(
769 label=_('Device type'),
770 queryset=DeviceType.objects.all(),
771 required=False,
772 context={
773 'parent': 'manufacturer',
774 },
775 query_params={
776 'manufacturer_id': '$manufacturer'
777 }
778 )
779 role = DynamicModelChoiceField(
780 label=_('Device role'),
781 queryset=DeviceRole.objects.all(),
782 required=False
783 )
784 site = DynamicModelChoiceField(
785 label=_('Site'),
786 queryset=Site.objects.all(),
787 required=False
788 )
789 location = DynamicModelChoiceField(
790 label=_('Location'),
791 queryset=Location.objects.all(),
792 required=False,
793 query_params={
794 'site_id': '$site'
795 }
796 )
797 tenant = DynamicModelChoiceField(
798 label=_('Tenant'),
799 queryset=Tenant.objects.all(),
800 required=False
801 )
802 platform = DynamicModelChoiceField(
803 label=_('Platform'),
804 queryset=Platform.objects.all(),
805 required=False
806 )
807 status = ChoiceField(
808 label=_('Status'),
809 choices=add_blank_choice(DeviceStatusChoices),
810 required=False
811 )
812 airflow = ChoiceField(
813 label=_('Airflow'),
814 choices=add_blank_choice(DeviceAirflowChoices),
815 required=False
816 )
817 cooling_method = forms.ChoiceField(
818 label=_('Cooling method'),
819 choices=add_blank_choice(CoolingMethodChoices),
820 required=False
821 )
822 serial = forms.CharField(
823 max_length=50,
824 required=False,
825 label=_('Serial Number')
826 )
827 config_template = DynamicModelChoiceField(
828 label=_('Config template'),
829 queryset=ConfigTemplate.objects.all(),
830 required=False
831 )
832 cluster = DynamicModelChoiceField(
833 label=_('Cluster'),
834 queryset=Cluster.objects.all(),
835 required=False,
836 query_params={
837 'site_id': ['$site', 'null']
838 },
839 )
841 model = Device
842 fieldsets = (
843 FieldSet('role', 'status', 'tenant', 'platform', 'description', name=_('Device')),
844 FieldSet('site', 'location', name=_('Location')),
845 FieldSet('manufacturer', 'device_type', 'airflow', 'cooling_method', 'serial', name=_('Hardware')),
846 FieldSet('config_template', name=_('Configuration')),
847 FieldSet('cluster', name=_('Virtualization')),
848 )
849 nullable_fields = (
850 'location', 'tenant', 'platform', 'serial', 'airflow', 'cooling_method', 'description', 'cluster', 'comments',
851 )
854class ModuleBulkEditForm(PrimaryModelBulkEditForm):
855 manufacturer = DynamicModelChoiceField(
856 label=_('Manufacturer'),
857 queryset=Manufacturer.objects.all(),
858 required=False
859 )
860 module_type = DynamicModelChoiceField(
861 label=_('Module type'),
862 queryset=ModuleType.objects.all(),
863 required=False,
864 query_params={
865 'manufacturer_id': '$manufacturer'
866 },
867 context={
868 'parent': 'manufacturer',
869 }
870 )
871 status = ChoiceField(
872 label=_('Status'),
873 choices=add_blank_choice(ModuleStatusChoices),
874 required=False,
875 initial=''
876 )
877 serial = forms.CharField(
878 max_length=50,
879 required=False,
880 label=_('Serial Number')
881 )
883 model = Module
884 fieldsets = (
885 FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'),
886 )
887 nullable_fields = ('serial', 'description', 'comments')
890class CableBundleBulkEditForm(PrimaryModelBulkEditForm):
891 pk = forms.ModelMultipleChoiceField(
892 queryset=CableBundle.objects.all(),
893 widget=forms.MultipleHiddenInput
894 )
895 description = forms.CharField(
896 label=_('Description'),
897 max_length=200,
898 required=False,
899 )
901 model = CableBundle
902 fieldsets = (
903 FieldSet('description',),
904 )
905 nullable_fields = ('description', 'comments')
908class CableBulkEditForm(PrimaryModelBulkEditForm):
909 type = ChoiceField(
910 label=_('Type'),
911 choices=add_blank_choice(CableTypeChoices),
912 required=False,
913 initial=''
914 )
915 status = ChoiceField(
916 label=_('Status'),
917 choices=add_blank_choice(LinkStatusChoices),
918 required=False,
919 initial=''
920 )
921 profile = ChoiceField(
922 label=_('Profile'),
923 choices=add_blank_choice(CableProfileChoices),
924 required=False,
925 initial=''
926 )
927 tenant = DynamicModelChoiceField(
928 label=_('Tenant'),
929 queryset=Tenant.objects.all(),
930 required=False
931 )
932 bundle = DynamicModelChoiceField(
933 label=_('Bundle'),
934 queryset=CableBundle.objects.all(),
935 required=False,
936 )
937 label = forms.CharField(
938 label=_('Label'),
939 max_length=100,
940 required=False
941 )
942 color = ColorField(
943 label=_('Color'),
944 required=False
945 )
946 length = forms.DecimalField(
947 label=_('Length'),
948 min_value=0,
949 required=False
950 )
951 length_unit = ChoiceField(
952 label=_('Length unit'),
953 choices=add_blank_choice(CableLengthUnitChoices),
954 required=False,
955 initial=''
956 )
958 model = Cable
959 fieldsets = (
960 FieldSet('type', 'status', 'profile', 'tenant', 'bundle', 'label', 'description'),
961 FieldSet('color', 'length', 'length_unit', name=_('Attributes')),
962 )
963 nullable_fields = (
964 'type', 'status', 'profile', 'tenant', 'bundle', 'label', 'color', 'length', 'description', 'comments',
965 )
968class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm):
969 domain = forms.CharField(
970 label=_('Domain'),
971 max_length=30,
972 required=False
973 )
975 model = VirtualChassis
976 fieldsets = (
977 FieldSet('domain', 'description'),
978 )
979 nullable_fields = ('domain', 'description', 'comments')
982class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
983 region = DynamicModelChoiceField(
984 label=_('Region'),
985 queryset=Region.objects.all(),
986 required=False,
987 initial_params={
988 'sites': '$site'
989 }
990 )
991 site_group = DynamicModelChoiceField(
992 label=_('Site group'),
993 queryset=SiteGroup.objects.all(),
994 required=False,
995 initial_params={
996 'sites': '$site'
997 }
998 )
999 site = DynamicModelChoiceField(
1000 label=_('Site'),
1001 queryset=Site.objects.all(),
1002 required=False,
1003 query_params={
1004 'region_id': '$region',
1005 'group_id': '$site_group',
1006 }
1007 )
1008 location = DynamicModelChoiceField(
1009 label=_('Location'),
1010 queryset=Location.objects.all(),
1011 required=False,
1012 query_params={
1013 'site_id': '$site'
1014 }
1015 )
1017 model = PowerPanel
1018 fieldsets = (
1019 FieldSet('region', 'site_group', 'site', 'location', 'description'),
1020 )
1021 nullable_fields = ('location', 'description', 'comments')
1024class PowerFeedBulkEditForm(PrimaryModelBulkEditForm):
1025 power_panel = DynamicModelChoiceField(
1026 label=_('Power panel'),
1027 queryset=PowerPanel.objects.all(),
1028 required=False
1029 )
1030 rack = DynamicModelChoiceField(
1031 label=_('Rack'),
1032 queryset=Rack.objects.all(),
1033 required=False,
1034 )
1035 status = ChoiceField(
1036 label=_('Status'),
1037 choices=add_blank_choice(PowerFeedStatusChoices),
1038 required=False,
1039 initial=''
1040 )
1041 type = ChoiceField(
1042 label=_('Type'),
1043 choices=add_blank_choice(PowerFeedTypeChoices),
1044 required=False,
1045 initial=''
1046 )
1047 supply = ChoiceField(
1048 label=_('Supply'),
1049 choices=add_blank_choice(PowerFeedSupplyChoices),
1050 required=False,
1051 initial=''
1052 )
1053 phase = ChoiceField(
1054 label=_('Phase'),
1055 choices=add_blank_choice(PowerFeedPhaseChoices),
1056 required=False,
1057 initial=''
1058 )
1059 voltage = forms.IntegerField(
1060 label=_('Voltage'),
1061 required=False
1062 )
1063 amperage = forms.IntegerField(
1064 label=_('Amperage'),
1065 required=False
1066 )
1067 max_utilization = forms.IntegerField(
1068 label=_('Max utilization'),
1069 required=False
1070 )
1071 mark_connected = forms.NullBooleanField(
1072 label=_('Mark connected'),
1073 required=False,
1074 widget=BulkEditNullBooleanSelect
1075 )
1076 tenant = DynamicModelChoiceField(
1077 queryset=Tenant.objects.all(),
1078 required=False
1079 )
1081 model = PowerFeed
1082 fieldsets = (
1083 FieldSet('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant'),
1084 FieldSet('supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Power'))
1085 )
1086 nullable_fields = ('rack', 'tenant', 'description', 'comments')
1089#
1090# Cooling
1091#
1093class CoolingSourceBulkEditForm(PrimaryModelBulkEditForm):
1094 region = DynamicModelChoiceField(
1095 label=_('Region'),
1096 queryset=Region.objects.all(),
1097 required=False,
1098 initial_params={
1099 'sites': '$site'
1100 }
1101 )
1102 site_group = DynamicModelChoiceField(
1103 label=_('Site group'),
1104 queryset=SiteGroup.objects.all(),
1105 required=False,
1106 initial_params={
1107 'sites': '$site'
1108 }
1109 )
1110 site = DynamicModelChoiceField(
1111 label=_('Site'),
1112 queryset=Site.objects.all(),
1113 required=False,
1114 query_params={
1115 'region_id': '$region',
1116 'group_id': '$site_group',
1117 }
1118 )
1119 location = DynamicModelChoiceField(
1120 label=_('Location'),
1121 queryset=Location.objects.all(),
1122 required=False,
1123 query_params={
1124 'site_id': '$site'
1125 }
1126 )
1127 type = forms.ChoiceField(
1128 label=_('Type'),
1129 choices=add_blank_choice(CoolingSourceTypeChoices),
1130 required=False,
1131 initial=''
1132 )
1133 status = forms.ChoiceField(
1134 label=_('Status'),
1135 choices=add_blank_choice(CoolingSourceStatusChoices),
1136 required=False,
1137 initial=''
1138 )
1139 fluid_type = forms.ChoiceField(
1140 label=_('Fluid type'),
1141 choices=add_blank_choice(FluidTypeChoices),
1142 required=False,
1143 initial=''
1144 )
1145 cooling_capacity = forms.DecimalField(
1146 label=_('Cooling capacity'),
1147 min_value=0,
1148 required=False
1149 )
1151 model = CoolingSource
1152 fieldsets = (
1153 FieldSet('region', 'site_group', 'site', 'location', 'type', 'status', 'description'),
1154 FieldSet('fluid_type', 'cooling_capacity', name=_('Characteristics')),
1155 )
1156 nullable_fields = (
1157 'location', 'fluid_type', 'cooling_capacity', 'description', 'comments',
1158 )
1161class CoolingFeedBulkEditForm(PrimaryModelBulkEditForm):
1162 cooling_source = DynamicModelChoiceField(
1163 label=_('Cooling source'),
1164 queryset=CoolingSource.objects.all(),
1165 required=False
1166 )
1167 rack = DynamicModelChoiceField(
1168 label=_('Rack'),
1169 queryset=Rack.objects.all(),
1170 required=False,
1171 )
1172 status = forms.ChoiceField(
1173 label=_('Status'),
1174 choices=add_blank_choice(CoolingFeedStatusChoices),
1175 required=False,
1176 initial=''
1177 )
1178 cooling_capacity = forms.DecimalField(
1179 label=_('Cooling capacity'),
1180 min_value=0,
1181 required=False
1182 )
1183 max_flow = forms.DecimalField(
1184 label=_('Max flow'),
1185 min_value=Decimal('0.01'),
1186 required=False
1187 )
1188 max_flow_unit = forms.ChoiceField(
1189 label=_('Max flow unit'),
1190 choices=add_blank_choice(FlowRateUnitChoices),
1191 required=False,
1192 initial=''
1193 )
1194 tenant = DynamicModelChoiceField(
1195 queryset=Tenant.objects.all(),
1196 required=False
1197 )
1199 model = CoolingFeed
1200 fieldsets = (
1201 FieldSet('cooling_source', 'rack', 'status', 'description', 'tenant'),
1202 FieldSet(
1203 'cooling_capacity', 'max_flow', 'max_flow_unit',
1204 name=_('Characteristics')
1205 ),
1206 )
1207 nullable_fields = (
1208 'rack', 'cooling_capacity', 'max_flow', 'max_flow_unit',
1209 'tenant', 'description', 'comments',
1210 )
1213#
1214# Device component templates
1215#
1217class ComponentTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
1218 pass
1221class ConsolePortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1222 pk = forms.ModelMultipleChoiceField(
1223 queryset=ConsolePortTemplate.objects.all(),
1224 widget=forms.MultipleHiddenInput()
1225 )
1226 label = forms.CharField(
1227 label=_('Label'),
1228 max_length=64,
1229 required=False
1230 )
1231 type = ChoiceField(
1232 label=_('Type'),
1233 choices=add_blank_choice(ConsolePortTypeChoices),
1234 required=False
1235 )
1236 description = forms.CharField(
1237 label=_('Description'),
1238 required=False
1239 )
1241 nullable_fields = ('label', 'type', 'description')
1244class ConsoleServerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1245 pk = forms.ModelMultipleChoiceField(
1246 queryset=ConsoleServerPortTemplate.objects.all(),
1247 widget=forms.MultipleHiddenInput()
1248 )
1249 label = forms.CharField(
1250 label=_('Label'),
1251 max_length=64,
1252 required=False
1253 )
1254 type = ChoiceField(
1255 label=_('Type'),
1256 choices=add_blank_choice(ConsolePortTypeChoices),
1257 required=False
1258 )
1259 description = forms.CharField(
1260 label=_('Description'),
1261 required=False
1262 )
1264 nullable_fields = ('label', 'type', 'description')
1267class PowerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1268 pk = forms.ModelMultipleChoiceField(
1269 queryset=PowerPortTemplate.objects.all(),
1270 widget=forms.MultipleHiddenInput()
1271 )
1272 label = forms.CharField(
1273 label=_('Label'),
1274 max_length=64,
1275 required=False
1276 )
1277 type = ChoiceField(
1278 label=_('Type'),
1279 choices=add_blank_choice(PowerPortTypeChoices),
1280 required=False
1281 )
1282 maximum_draw = forms.IntegerField(
1283 label=_('Maximum draw'),
1284 min_value=1,
1285 required=False,
1286 help_text=_("Maximum power draw (watts)")
1287 )
1288 allocated_draw = forms.IntegerField(
1289 label=_('Allocated draw'),
1290 min_value=1,
1291 required=False,
1292 help_text=_("Allocated power draw (watts)")
1293 )
1294 description = forms.CharField(
1295 label=_('Description'),
1296 required=False
1297 )
1299 nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
1302class PowerOutletTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1303 pk = forms.ModelMultipleChoiceField(
1304 queryset=PowerOutletTemplate.objects.all(),
1305 widget=forms.MultipleHiddenInput()
1306 )
1307 device_type = forms.ModelChoiceField(
1308 label=_('Device type'),
1309 queryset=DeviceType.objects.all(),
1310 required=False,
1311 disabled=True,
1312 widget=forms.HiddenInput()
1313 )
1314 label = forms.CharField(
1315 label=_('Label'),
1316 max_length=64,
1317 required=False
1318 )
1319 type = ChoiceField(
1320 label=_('Type'),
1321 choices=add_blank_choice(PowerOutletTypeChoices),
1322 required=False
1323 )
1324 color = ColorField(
1325 label=_('Color'),
1326 required=False
1327 )
1328 power_port = forms.ModelChoiceField(
1329 label=_('Power port'),
1330 queryset=PowerPortTemplate.objects.all(),
1331 required=False
1332 )
1333 feed_leg = ChoiceField(
1334 label=_('Feed leg'),
1335 choices=add_blank_choice(PowerOutletFeedLegChoices),
1336 required=False
1337 )
1338 description = forms.CharField(
1339 label=_('Description'),
1340 required=False
1341 )
1343 nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
1345 def __init__(self, *args, **kwargs):
1346 super().__init__(*args, **kwargs)
1348 # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
1349 if 'device_type' in self.initial:
1350 device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
1351 self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
1352 else:
1353 self.fields['power_port'].choices = ()
1354 self.fields['power_port'].widget.attrs['disabled'] = True
1357class CoolingIntakeTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1358 pk = forms.ModelMultipleChoiceField(
1359 queryset=CoolingIntakeTemplate.objects.all(),
1360 widget=forms.MultipleHiddenInput()
1361 )
1362 label = forms.CharField(
1363 label=_('Label'),
1364 max_length=64,
1365 required=False
1366 )
1367 type = forms.ChoiceField(
1368 label=_('Type'),
1369 choices=add_blank_choice(CoolingConnectorTypeChoices),
1370 required=False
1371 )
1372 diameter = forms.DecimalField(
1373 label=_('Diameter'),
1374 min_value=Decimal('0.01'),
1375 required=False
1376 )
1377 diameter_unit = forms.ChoiceField(
1378 label=_('Diameter unit'),
1379 choices=add_blank_choice(DiameterUnitChoices),
1380 required=False
1381 )
1382 max_flow = forms.DecimalField(
1383 label=_('Max flow'),
1384 min_value=Decimal('0.01'),
1385 required=False
1386 )
1387 max_flow_unit = forms.ChoiceField(
1388 label=_('Max flow unit'),
1389 choices=add_blank_choice(FlowRateUnitChoices),
1390 required=False
1391 )
1392 description = forms.CharField(
1393 label=_('Description'),
1394 required=False
1395 )
1397 fieldsets = (
1398 FieldSet(
1399 'label', 'type',
1400 InlineFields('diameter', 'diameter_unit', label=_('Diameter')),
1401 InlineFields('max_flow', 'max_flow_unit', label=_('Max flow')),
1402 'description',
1403 ),
1404 )
1405 nullable_fields = (
1406 'label', 'type', 'diameter', 'diameter_unit', 'max_flow', 'max_flow_unit',
1407 'description',
1408 )
1411class CoolingOutflowTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1412 pk = forms.ModelMultipleChoiceField(
1413 queryset=CoolingOutflowTemplate.objects.all(),
1414 widget=forms.MultipleHiddenInput()
1415 )
1416 device_type = forms.ModelChoiceField(
1417 label=_('Device type'),
1418 queryset=DeviceType.objects.all(),
1419 required=False,
1420 disabled=True,
1421 widget=forms.HiddenInput()
1422 )
1423 label = forms.CharField(
1424 label=_('Label'),
1425 max_length=64,
1426 required=False
1427 )
1428 type = forms.ChoiceField(
1429 label=_('Type'),
1430 choices=add_blank_choice(CoolingConnectorTypeChoices),
1431 required=False
1432 )
1433 diameter = forms.DecimalField(
1434 label=_('Diameter'),
1435 min_value=Decimal('0.01'),
1436 required=False
1437 )
1438 diameter_unit = forms.ChoiceField(
1439 label=_('Diameter unit'),
1440 choices=add_blank_choice(DiameterUnitChoices),
1441 required=False
1442 )
1443 cooling_intake = forms.ModelChoiceField(
1444 label=_('Cooling intake'),
1445 queryset=CoolingIntakeTemplate.objects.all(),
1446 required=False
1447 )
1448 description = forms.CharField(
1449 label=_('Description'),
1450 required=False
1451 )
1453 fieldsets = (
1454 FieldSet(
1455 'label', 'type',
1456 InlineFields('diameter', 'diameter_unit', label=_('Diameter')),
1457 'cooling_intake', 'description',
1458 ),
1459 )
1460 nullable_fields = (
1461 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake', 'description',
1462 )
1464 def __init__(self, *args, **kwargs):
1465 super().__init__(*args, **kwargs)
1467 # Limit cooling_intake queryset to CoolingIntakeTemplates which belong to the parent DeviceType
1468 if 'device_type' in self.initial:
1469 device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
1470 self.fields['cooling_intake'].queryset = CoolingIntakeTemplate.objects.filter(device_type=device_type)
1471 else:
1472 self.fields['cooling_intake'].choices = ()
1473 self.fields['cooling_intake'].widget.attrs['disabled'] = True
1476class InterfaceTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1477 pk = forms.ModelMultipleChoiceField(
1478 queryset=InterfaceTemplate.objects.all(),
1479 widget=forms.MultipleHiddenInput()
1480 )
1481 label = forms.CharField(
1482 label=_('Label'),
1483 max_length=64,
1484 required=False
1485 )
1486 type = ChoiceField(
1487 label=_('Type'),
1488 choices=add_blank_choice(InterfaceTypeChoices),
1489 required=False
1490 )
1491 channels = forms.IntegerField(
1492 label=_('Channels'),
1493 required=False
1494 )
1495 channel_id = forms.IntegerField(
1496 label=_('Channel ID'),
1497 required=False
1498 )
1499 enabled = forms.NullBooleanField(
1500 label=_('Enabled'),
1501 required=False,
1502 widget=BulkEditNullBooleanSelect
1503 )
1504 mgmt_only = forms.NullBooleanField(
1505 required=False,
1506 widget=BulkEditNullBooleanSelect,
1507 label=_('Management only')
1508 )
1509 description = forms.CharField(
1510 label=_('Description'),
1511 required=False
1512 )
1513 poe_mode = ChoiceField(
1514 choices=add_blank_choice(InterfacePoEModeChoices),
1515 required=False,
1516 initial='',
1517 label=_('PoE mode')
1518 )
1519 poe_type = ChoiceField(
1520 choices=add_blank_choice(InterfacePoETypeChoices),
1521 required=False,
1522 initial='',
1523 label=_('PoE type')
1524 )
1525 rf_role = ChoiceField(
1526 choices=add_blank_choice(WirelessRoleChoices),
1527 required=False,
1528 initial='',
1529 label=_('Wireless role')
1530 )
1532 nullable_fields = ('label', 'channels', 'channel_id', 'description', 'poe_mode', 'poe_type', 'rf_role')
1535class FrontPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1536 pk = forms.ModelMultipleChoiceField(
1537 queryset=FrontPortTemplate.objects.all(),
1538 widget=forms.MultipleHiddenInput()
1539 )
1540 label = forms.CharField(
1541 label=_('Label'),
1542 max_length=64,
1543 required=False
1544 )
1545 type = ChoiceField(
1546 label=_('Type'),
1547 choices=add_blank_choice(PortTypeChoices),
1548 required=False
1549 )
1550 color = ColorField(
1551 label=_('Color'),
1552 required=False
1553 )
1554 description = forms.CharField(
1555 label=_('Description'),
1556 required=False
1557 )
1559 nullable_fields = ('description',)
1562class RearPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1563 pk = forms.ModelMultipleChoiceField(
1564 queryset=RearPortTemplate.objects.all(),
1565 widget=forms.MultipleHiddenInput()
1566 )
1567 label = forms.CharField(
1568 label=_('Label'),
1569 max_length=64,
1570 required=False
1571 )
1572 type = ChoiceField(
1573 label=_('Type'),
1574 choices=add_blank_choice(PortTypeChoices),
1575 required=False
1576 )
1577 color = ColorField(
1578 label=_('Color'),
1579 required=False
1580 )
1581 description = forms.CharField(
1582 label=_('Description'),
1583 required=False
1584 )
1586 nullable_fields = ('description',)
1589class ModuleBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1590 pk = forms.ModelMultipleChoiceField(
1591 queryset=ModuleBayTemplate.objects.all(),
1592 widget=forms.MultipleHiddenInput()
1593 )
1594 label = forms.CharField(
1595 label=_('Label'),
1596 max_length=64,
1597 required=False
1598 )
1599 position = forms.CharField(
1600 label=_('Position'),
1601 max_length=30,
1602 required=False
1603 )
1604 description = forms.CharField(
1605 label=_('Description'),
1606 required=False
1607 )
1608 enabled = forms.NullBooleanField(
1609 label=_('Enabled'),
1610 required=False,
1611 widget=BulkEditNullBooleanSelect,
1612 )
1613 add_module_bay_types = DynamicModelMultipleChoiceField(
1614 label=_('Add bay types'),
1615 queryset=ModuleBayType.objects.all(),
1616 required=False,
1617 )
1618 remove_module_bay_types = DynamicModelMultipleChoiceField(
1619 label=_('Remove bay types'),
1620 queryset=ModuleBayType.objects.all(),
1621 required=False,
1622 )
1624 fieldsets = (
1625 FieldSet('label', 'enabled', 'description'),
1626 FieldSet('add_module_bay_types', 'remove_module_bay_types', name=_('Bay Types')),
1627 )
1628 nullable_fields = ('label', 'position', 'description')
1631class DeviceBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1632 pk = forms.ModelMultipleChoiceField(
1633 queryset=DeviceBayTemplate.objects.all(),
1634 widget=forms.MultipleHiddenInput()
1635 )
1636 label = forms.CharField(
1637 label=_('Label'),
1638 max_length=64,
1639 required=False
1640 )
1641 description = forms.CharField(
1642 label=_('Description'),
1643 required=False
1644 )
1645 enabled = forms.NullBooleanField(
1646 label=_('Enabled'),
1647 required=False,
1648 widget=BulkEditNullBooleanSelect,
1649 )
1651 nullable_fields = ('label', 'description')
1654class InventoryItemTemplateBulkEditForm(ComponentTemplateBulkEditForm):
1655 pk = forms.ModelMultipleChoiceField(
1656 queryset=InventoryItemTemplate.objects.all(),
1657 widget=forms.MultipleHiddenInput()
1658 )
1659 label = forms.CharField(
1660 label=_('Label'),
1661 max_length=64,
1662 required=False
1663 )
1664 description = forms.CharField(
1665 label=_('Description'),
1666 required=False
1667 )
1668 role = DynamicModelChoiceField(
1669 label=_('Role'),
1670 queryset=InventoryItemRole.objects.all(),
1671 required=False
1672 )
1673 manufacturer = DynamicModelChoiceField(
1674 label=_('Manufacturer'),
1675 queryset=Manufacturer.objects.all(),
1676 required=False
1677 )
1678 part_id = forms.CharField(
1679 label=_('Part ID'),
1680 max_length=50,
1681 required=False
1682 )
1684 nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
1687#
1688# Device components
1689#
1691class ComponentBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
1692 device = forms.ModelChoiceField(
1693 label=_('Device'),
1694 queryset=Device.objects.all(),
1695 required=False,
1696 disabled=True,
1697 widget=forms.HiddenInput()
1698 )
1699 module = forms.ModelChoiceField(
1700 label=_('Module'),
1701 queryset=Module.objects.all(),
1702 required=False
1703 )
1705 def __init__(self, *args, initial=None, **kwargs):
1706 try:
1707 self.device_id = int(initial.get('device'))
1708 except (TypeError, ValueError):
1709 self.device_id = None
1711 super().__init__(*args, initial=initial, **kwargs)
1713 # Limit module queryset to Modules which belong to the parent Device
1714 if self.device_id:
1715 device = Device.objects.filter(pk=self.device_id).first()
1716 self.fields['module'].queryset = Module.objects.filter(device=device)
1717 else:
1718 self.fields['module'].choices = ()
1719 self.fields['module'].widget.attrs['disabled'] = True
1722class ConsolePortBulkEditForm(
1723 ComponentBulkEditForm,
1724 form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description'])
1725):
1726 mark_connected = forms.NullBooleanField(
1727 label=_('Mark connected'),
1728 required=False,
1729 widget=BulkEditNullBooleanSelect
1730 )
1732 model = ConsolePort
1733 fieldsets = (
1734 FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
1735 )
1736 nullable_fields = ('module', 'label', 'description')
1739class ConsoleServerPortBulkEditForm(
1740 ComponentBulkEditForm,
1741 form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description'])
1742):
1743 mark_connected = forms.NullBooleanField(
1744 label=_('Mark connected'),
1745 required=False,
1746 widget=BulkEditNullBooleanSelect
1747 )
1749 model = ConsoleServerPort
1750 fieldsets = (
1751 FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
1752 )
1753 nullable_fields = ('module', 'label', 'description')
1756class PowerPortBulkEditForm(
1757 ComponentBulkEditForm,
1758 form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description'])
1759):
1760 mark_connected = forms.NullBooleanField(
1761 label=_('Mark connected'),
1762 required=False,
1763 widget=BulkEditNullBooleanSelect
1764 )
1766 model = PowerPort
1767 fieldsets = (
1768 FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
1769 FieldSet('maximum_draw', 'allocated_draw', name=_('Power')),
1770 )
1771 nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')
1774class PowerOutletBulkEditForm(
1775 ComponentBulkEditForm,
1776 form_from_model(
1777 PowerOutlet,
1778 ['label', 'type', 'status', 'color', 'feed_leg', 'power_port', 'mark_connected', 'description']
1779 )
1780):
1781 mark_connected = forms.NullBooleanField(
1782 label=_('Mark connected'),
1783 required=False,
1784 widget=BulkEditNullBooleanSelect
1785 )
1787 model = PowerOutlet
1788 fieldsets = (
1789 FieldSet('module', 'type', 'label', 'status', 'description', 'mark_connected', 'color'),
1790 FieldSet('feed_leg', 'power_port', name=_('Power')),
1791 )
1792 nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
1794 def __init__(self, *args, **kwargs):
1795 super().__init__(*args, **kwargs)
1797 # Limit power_port queryset to PowerPorts which belong to the parent Device
1798 if self.device_id:
1799 device = Device.objects.filter(pk=self.device_id).first()
1800 self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
1801 else:
1802 self.fields['power_port'].choices = ()
1803 self.fields['power_port'].widget.attrs['disabled'] = True
1806class CoolingIntakeBulkEditForm(
1807 ComponentBulkEditForm,
1808 form_from_model(CoolingIntake, [
1809 'label', 'type', 'diameter', 'diameter_unit', 'max_flow', 'max_flow_unit',
1810 'description'
1811 ])
1812):
1813 cooling_outflow = DynamicModelChoiceField(
1814 label=_('Cooling outflow'),
1815 queryset=CoolingOutflow.objects.all(),
1816 required=False
1817 )
1819 model = CoolingIntake
1820 fieldsets = (
1821 FieldSet(
1822 'module', 'type',
1823 InlineFields('diameter', 'diameter_unit', label=_('Diameter')),
1824 'label', 'cooling_outflow', 'description',
1825 ),
1826 FieldSet(
1827 InlineFields('max_flow', 'max_flow_unit', label=_('Max flow')),
1828 name=_('Characteristics')
1829 ),
1830 )
1831 nullable_fields = (
1832 'module', 'label', 'type', 'diameter', 'diameter_unit', 'max_flow', 'max_flow_unit',
1833 'cooling_outflow', 'description',
1834 )
1837class CoolingOutflowBulkEditForm(
1838 ComponentBulkEditForm,
1839 form_from_model(
1840 CoolingOutflow,
1841 [
1842 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake',
1843 'description'
1844 ]
1845 )
1846):
1847 model = CoolingOutflow
1848 fieldsets = (
1849 FieldSet(
1850 'module', 'type',
1851 InlineFields('diameter', 'diameter_unit', label=_('Diameter')),
1852 'label', 'description', 'cooling_intake',
1853 ),
1854 )
1855 nullable_fields = (
1856 'module', 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake', 'description',
1857 )
1859 def __init__(self, *args, **kwargs):
1860 super().__init__(*args, **kwargs)
1862 # Limit cooling_intake queryset to CoolingIntakes which belong to the parent Device
1863 if self.device_id:
1864 device = Device.objects.filter(pk=self.device_id).first()
1865 self.fields['cooling_intake'].queryset = CoolingIntake.objects.filter(device=device)
1866 else:
1867 self.fields['cooling_intake'].choices = ()
1868 self.fields['cooling_intake'].widget.attrs['disabled'] = True
1871class InterfaceBulkEditForm(
1872 ComponentBulkEditForm,
1873 form_from_model(Interface, [
1874 'label', 'type', 'channels', 'channel_id', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'mtu',
1875 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
1876 'rf_channel_width', 'tx_power', 'wireless_lans', 'vlan_translation_policy'
1877 ])
1878):
1879 enabled = forms.NullBooleanField(
1880 label=_('Enabled'),
1881 required=False,
1882 widget=BulkEditNullBooleanSelect
1883 )
1884 parent = DynamicModelChoiceField(
1885 label=_('Parent'),
1886 queryset=Interface.objects.all(),
1887 required=False,
1888 query_params={
1889 'virtual_chassis_member_id': '$device',
1890 }
1891 )
1892 bridge = DynamicModelChoiceField(
1893 label=_('Bridge'),
1894 queryset=Interface.objects.all(),
1895 required=False,
1896 query_params={
1897 'virtual_chassis_member_id': '$device',
1898 }
1899 )
1900 lag = DynamicModelChoiceField(
1901 queryset=Interface.objects.all(),
1902 required=False,
1903 query_params={
1904 'type': 'lag',
1905 'virtual_chassis_member_id': '$device',
1906 },
1907 label=_('LAG')
1908 )
1909 vdcs = DynamicModelMultipleChoiceField(
1910 queryset=VirtualDeviceContext.objects.all(),
1911 required=False,
1912 label=_('Virtual device contexts'),
1913 query_params={
1914 'device_id': '$device',
1915 }
1916 )
1917 speed = PositiveBigIntegerField(
1918 label=_('Speed'),
1919 required=False,
1920 widget=NumberWithOptions(
1921 options=InterfaceSpeedChoices
1922 )
1923 )
1924 mgmt_only = forms.NullBooleanField(
1925 required=False,
1926 widget=BulkEditNullBooleanSelect,
1927 label=_('Management only')
1928 )
1929 poe_mode = ChoiceField(
1930 choices=add_blank_choice(InterfacePoEModeChoices),
1931 required=False,
1932 initial='',
1933 label=_('PoE mode')
1934 )
1935 poe_type = ChoiceField(
1936 choices=add_blank_choice(InterfacePoETypeChoices),
1937 required=False,
1938 initial='',
1939 label=_('PoE type')
1940 )
1941 mark_connected = forms.NullBooleanField(
1942 label=_('Mark connected'),
1943 required=False,
1944 widget=BulkEditNullBooleanSelect
1945 )
1946 mode = ChoiceField(
1947 label=_('Mode'),
1948 choices=add_blank_choice(InterfaceModeChoices),
1949 required=False,
1950 initial=''
1951 )
1952 vlan_group = DynamicModelChoiceField(
1953 queryset=VLANGroup.objects.all(),
1954 required=False,
1955 label=_('VLAN group')
1956 )
1957 untagged_vlan = DynamicModelChoiceField(
1958 queryset=VLAN.objects.all(),
1959 required=False,
1960 query_params={
1961 'group_id': '$vlan_group',
1962 'available_on_device': '$device',
1963 },
1964 label=_('Untagged VLAN')
1965 )
1966 tagged_vlans = DynamicModelMultipleChoiceField(
1967 queryset=VLAN.objects.all(),
1968 required=False,
1969 query_params={
1970 'group_id': '$vlan_group',
1971 'available_on_device': '$device',
1972 },
1973 label=_('Tagged VLANs')
1974 )
1975 add_tagged_vlans = DynamicModelMultipleChoiceField(
1976 label=_('Add tagged VLANs'),
1977 queryset=VLAN.objects.all(),
1978 required=False,
1979 query_params={
1980 'group_id': '$vlan_group',
1981 'available_on_device': '$device',
1982 },
1983 )
1984 remove_tagged_vlans = DynamicModelMultipleChoiceField(
1985 label=_('Remove tagged VLANs'),
1986 queryset=VLAN.objects.all(),
1987 required=False,
1988 query_params={
1989 'group_id': '$vlan_group',
1990 'available_on_device': '$device',
1991 }
1992 )
1993 qinq_svlan = DynamicModelChoiceField(
1994 queryset=VLAN.objects.all(),
1995 required=False,
1996 label=_('Q-in-Q Service VLAN'),
1997 query_params={
1998 'group_id': '$vlan_group',
1999 'available_on_device': '$device',
2000 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
2001 }
2002 )
2003 vrf = DynamicModelChoiceField(
2004 queryset=VRF.objects.all(),
2005 required=False,
2006 label=_('VRF')
2007 )
2008 wireless_lan_group = DynamicModelChoiceField(
2009 queryset=WirelessLANGroup.objects.all(),
2010 required=False,
2011 label=_('Wireless LAN group')
2012 )
2013 wireless_lans = DynamicModelMultipleChoiceField(
2014 queryset=WirelessLAN.objects.all(),
2015 required=False,
2016 label=_('Wireless LANs'),
2017 query_params={
2018 'group_id': '$wireless_lan_group',
2019 }
2020 )
2022 model = Interface
2023 fieldsets = (
2024 FieldSet('module', 'type', 'channels', 'label', 'speed', 'duplex', 'description'),
2025 FieldSet('vrf', 'wwn', name=_('Addressing')),
2026 FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
2027 FieldSet('poe_mode', 'poe_type', name=_('PoE')),
2028 FieldSet('parent', 'channel_id', 'bridge', 'lag', name=_('Related Interfaces')),
2029 FieldSet(
2030 'mode', 'vlan_group', 'untagged_vlan', 'qinq_svlan', 'vlan_translation_policy', name=_('802.1Q Switching')
2031 ),
2032 FieldSet(
2033 TabbedGroups(
2034 FieldSet('tagged_vlans', name=_('Assignment')),
2035 FieldSet('add_tagged_vlans', 'remove_tagged_vlans', name=_('Add/Remove')),
2036 ),
2037 ),
2038 FieldSet(
2039 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
2040 name=_('Wireless')
2041 ),
2042 )
2043 nullable_fields = (
2044 'module', 'label', 'channels', 'channel_id', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'vdcs',
2045 'mtu', 'description', 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
2046 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'wireless_lans', 'vlan_translation_policy',
2047 )
2049 def __init__(self, *args, **kwargs):
2050 super().__init__(*args, **kwargs)
2051 if not self.device_id:
2052 # See #4523
2053 if 'pk' in self.initial:
2054 site = None
2055 interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
2057 # Check interface sites. First interface should set site, further interfaces will either continue the
2058 # loop or reset back to no site and break the loop.
2059 for interface in interfaces:
2060 if site is None:
2061 site = interface.device.site
2062 elif interface.device.site is not site:
2063 site = None
2064 break
2066 if site is not None:
2067 # Query for VLANs assigned to the same site and VLANs with no site assigned (null).
2068 self.fields['untagged_vlan'].widget.add_query_param(
2069 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
2070 )
2071 self.fields['tagged_vlans'].widget.add_query_param(
2072 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
2073 )
2075 self.fields['add_tagged_vlans'].widget.add_query_param(
2076 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
2077 )
2078 self.fields['remove_tagged_vlans'].widget.add_query_param(
2079 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
2080 )
2082 self.fields['parent'].choices = ()
2083 self.fields['parent'].widget.attrs['disabled'] = True
2084 self.fields['bridge'].choices = ()
2085 self.fields['bridge'].widget.attrs['disabled'] = True
2086 self.fields['lag'].choices = ()
2087 self.fields['lag'].widget.attrs['disabled'] = True
2089 def clean(self):
2090 super().clean()
2092 if not self.cleaned_data['mode']:
2093 if self.cleaned_data['untagged_vlan']:
2094 raise forms.ValidationError({'untagged_vlan': _("Interface mode must be specified to assign VLANs")})
2095 if self.cleaned_data['tagged_vlans']:
2096 raise forms.ValidationError({'tagged_vlans': _("Interface mode must be specified to assign VLANs")})
2098 # Untagged interfaces cannot be assigned tagged VLANs
2099 elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
2100 raise forms.ValidationError({
2101 'mode': _("An access interface cannot have tagged VLANs assigned.")
2102 })
2104 # Remove all tagged VLAN assignments from "tagged all" interfaces
2105 elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
2106 self.cleaned_data['tagged_vlans'] = []
2109class FrontPortBulkEditForm(
2110 ComponentBulkEditForm,
2111 form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description'])
2112):
2113 mark_connected = forms.NullBooleanField(
2114 label=_('Mark connected'),
2115 required=False,
2116 widget=BulkEditNullBooleanSelect
2117 )
2119 model = FrontPort
2120 fieldsets = (
2121 FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
2122 )
2123 nullable_fields = ('module', 'label', 'description', 'color')
2126class RearPortBulkEditForm(
2127 ComponentBulkEditForm,
2128 form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description'])
2129):
2130 mark_connected = forms.NullBooleanField(
2131 label=_('Mark connected'),
2132 required=False,
2133 widget=BulkEditNullBooleanSelect
2134 )
2136 model = RearPort
2137 fieldsets = (
2138 FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
2139 )
2140 nullable_fields = ('module', 'label', 'description', 'color')
2143class ModuleBayBulkEditForm(
2144 form_from_model(ModuleBay, ['label', 'position', 'description']),
2145 NetBoxModelBulkEditForm
2146):
2147 enabled = forms.NullBooleanField(
2148 label=_('Enabled'),
2149 required=False,
2150 widget=BulkEditNullBooleanSelect,
2151 )
2152 add_module_bay_types = DynamicModelMultipleChoiceField(
2153 label=_('Add bay types'),
2154 queryset=ModuleBayType.objects.all(),
2155 required=False,
2156 )
2157 remove_module_bay_types = DynamicModelMultipleChoiceField(
2158 label=_('Remove bay types'),
2159 queryset=ModuleBayType.objects.all(),
2160 required=False,
2161 )
2163 model = ModuleBay
2164 fieldsets = (
2165 FieldSet('label', 'position', 'enabled', 'description'),
2166 FieldSet('add_module_bay_types', 'remove_module_bay_types', name=_('Bay Types')),
2167 )
2168 nullable_fields = ('label', 'position', 'description')
2171class DeviceBayBulkEditForm(
2172 form_from_model(DeviceBay, ['label', 'description']),
2173 NetBoxModelBulkEditForm
2174):
2175 enabled = forms.NullBooleanField(
2176 label=_('Enabled'),
2177 required=False,
2178 widget=BulkEditNullBooleanSelect,
2179 )
2181 model = DeviceBay
2182 fieldsets = (
2183 FieldSet('label', 'enabled', 'description'),
2184 )
2185 nullable_fields = ('label', 'description')
2188class InventoryItemBulkEditForm(
2189 form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
2190 NetBoxModelBulkEditForm
2191):
2192 device = DynamicModelChoiceField(
2193 label=_('Device'),
2194 queryset=Device.objects.all(),
2195 required=False
2196 )
2197 role = DynamicModelChoiceField(
2198 label=_('Role'),
2199 queryset=InventoryItemRole.objects.all(),
2200 required=False
2201 )
2202 manufacturer = DynamicModelChoiceField(
2203 label=_('Manufacturer'),
2204 queryset=Manufacturer.objects.all(),
2205 required=False
2206 )
2207 status = ChoiceField(
2208 label=_('Status'),
2209 choices=add_blank_choice(InventoryItemStatusChoices),
2210 required=False,
2211 initial=''
2212 )
2214 model = InventoryItem
2215 fieldsets = (
2216 FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'status', 'description'),
2217 )
2218 nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
2220 def __init__(self, *args, **kwargs):
2221 super().__init__(*args, **kwargs)
2223 # Remove parent device passed as context to avoid conflicts with the actual device field
2224 # on this form (see bug #19464)
2225 self.initial.pop('device', None)
2228#
2229# Device component roles
2230#
2232class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm):
2233 color = ColorField(
2234 label=_('Color'),
2235 required=False
2236 )
2238 model = InventoryItemRole
2239 fieldsets = (
2240 FieldSet('color', 'description'),
2241 )
2242 nullable_fields = ('color', 'description', 'comments')
2245class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
2246 device = DynamicModelChoiceField(
2247 label=_('Device'),
2248 queryset=Device.objects.all(),
2249 required=False
2250 )
2251 status = ChoiceField(
2252 label=_('Status'),
2253 required=False,
2254 choices=add_blank_choice(VirtualDeviceContextStatusChoices)
2255 )
2256 tenant = DynamicModelChoiceField(
2257 label=_('Tenant'),
2258 queryset=Tenant.objects.all(),
2259 required=False
2260 )
2262 model = VirtualDeviceContext
2263 fieldsets = (
2264 FieldSet('device', 'status', 'tenant'),
2265 )
2266 nullable_fields = ('device', 'tenant', )
2268 def __init__(self, *args, **kwargs):
2269 super().__init__(*args, **kwargs)
2271 # The ?device=<id> GET param is navigation context (filter), not an intent to change the
2272 # device field — drop it from initial so Django's changed_data doesn't treat it as an edit.
2273 self.initial.pop('device', None)
2276#
2277# Addressing
2278#
2280class MACAddressBulkEditForm(PrimaryModelBulkEditForm):
2281 model = MACAddress
2282 fieldsets = (
2283 FieldSet('description'),
2284 )
2285 nullable_fields = ('description', 'comments')