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

1from decimal import Decimal 

2 

3from django import forms 

4from django.conf import settings 

5from django.utils.translation import gettext_lazy as _ 

6from timezone_field import TimeZoneFormField 

7 

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 

38 

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) 

93 

94 

95class RegionBulkEditForm(NestedGroupModelBulkEditForm): 

96 parent = DynamicModelChoiceField( 

97 label=_('Parent'), 

98 queryset=Region.objects.all(), 

99 required=False 

100 ) 

101 

102 model = Region 

103 fieldsets = ( 

104 FieldSet('parent', 'description'), 

105 ) 

106 nullable_fields = ('parent', 'description', 'comments') 

107 

108 

109class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm): 

110 parent = DynamicModelChoiceField( 

111 label=_('Parent'), 

112 queryset=SiteGroup.objects.all(), 

113 required=False 

114 ) 

115 

116 model = SiteGroup 

117 fieldsets = ( 

118 FieldSet('parent', 'description'), 

119 ) 

120 nullable_fields = ('parent', 'description', 'comments') 

121 

122 

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 ) 

174 

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 ) 

182 

183 

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 ) 

214 

215 model = Location 

216 fieldsets = ( 

217 FieldSet('site', 'parent', 'status', 'tenant', 'facility', 'description'), 

218 ) 

219 nullable_fields = ('parent', 'tenant', 'facility', 'description', 'comments') 

220 

221 

222class RackGroupBulkEditForm(OrganizationalModelBulkEditForm): 

223 model = RackGroup 

224 fieldsets = ( 

225 FieldSet('description'), 

226 ) 

227 nullable_fields = ('description', 'comments') 

228 

229 

230class RackRoleBulkEditForm(OrganizationalModelBulkEditForm): 

231 color = ColorField( 

232 label=_('Color'), 

233 required=False 

234 ) 

235 

236 model = RackRole 

237 fieldsets = ( 

238 FieldSet('color', 'description'), 

239 ) 

240 nullable_fields = ('color', 'description', 'comments') 

241 

242 

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 ) 

323 

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 ) 

340 

341 

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 ) 

487 

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 ) 

504 

505 

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 ) 

523 

524 model = RackReservation 

525 fieldsets = ( 

526 FieldSet('status', 'user', 'tenant', 'description'), 

527 ) 

528 nullable_fields = ('comments',) 

529 

530 

531class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm): 

532 model = Manufacturer 

533 fieldsets = ( 

534 FieldSet('description'), 

535 ) 

536 nullable_fields = ('description', 'comments') 

537 

538 

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 ) 

595 

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 ) 

608 

609 

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 ) 

620 

621 model = ModuleBayType 

622 fieldsets = ( 

623 FieldSet('manufacturer', 'color', 'description', name=_('Module Bay Type')), 

624 ) 

625 nullable_fields = ('manufacturer', 'color', 'description', 'comments') 

626 

627 

628class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm): 

629 schema = JSONField( 

630 label=_('Schema'), 

631 required=False 

632 ) 

633 

634 model = ModuleTypeProfile 

635 fieldsets = ( 

636 FieldSet('name', 'description', 'schema', name=_('Profile')), 

637 ) 

638 nullable_fields = ('description', 'comments') 

639 

640 

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 ) 

682 

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 ) 

693 

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 ) 

708 

709 

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 ) 

730 

731 model = DeviceRole 

732 fieldsets = ( 

733 FieldSet('parent', 'color', 'vm_role', 'config_template', 'description'), 

734 ) 

735 nullable_fields = ('parent', 'color', 'config_template', 'description', 'comments') 

736 

737 

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 ) 

754 

755 model = Platform 

756 fieldsets = ( 

757 FieldSet('parent', 'manufacturer', 'config_template', 'description'), 

758 ) 

759 nullable_fields = ('parent', 'manufacturer', 'config_template', 'description', 'comments') 

760 

761 

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 ) 

840 

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 ) 

852 

853 

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 ) 

882 

883 model = Module 

884 fieldsets = ( 

885 FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'), 

886 ) 

887 nullable_fields = ('serial', 'description', 'comments') 

888 

889 

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 ) 

900 

901 model = CableBundle 

902 fieldsets = ( 

903 FieldSet('description',), 

904 ) 

905 nullable_fields = ('description', 'comments') 

906 

907 

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 ) 

957 

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 ) 

966 

967 

968class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm): 

969 domain = forms.CharField( 

970 label=_('Domain'), 

971 max_length=30, 

972 required=False 

973 ) 

974 

975 model = VirtualChassis 

976 fieldsets = ( 

977 FieldSet('domain', 'description'), 

978 ) 

979 nullable_fields = ('domain', 'description', 'comments') 

980 

981 

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 ) 

1016 

1017 model = PowerPanel 

1018 fieldsets = ( 

1019 FieldSet('region', 'site_group', 'site', 'location', 'description'), 

1020 ) 

1021 nullable_fields = ('location', 'description', 'comments') 

1022 

1023 

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 ) 

1080 

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') 

1087 

1088 

1089# 

1090# Cooling 

1091# 

1092 

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 ) 

1150 

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 ) 

1159 

1160 

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 ) 

1198 

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 ) 

1211 

1212 

1213# 

1214# Device component templates 

1215# 

1216 

1217class ComponentTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm): 

1218 pass 

1219 

1220 

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 ) 

1240 

1241 nullable_fields = ('label', 'type', 'description') 

1242 

1243 

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 ) 

1263 

1264 nullable_fields = ('label', 'type', 'description') 

1265 

1266 

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 ) 

1298 

1299 nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description') 

1300 

1301 

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 ) 

1342 

1343 nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description') 

1344 

1345 def __init__(self, *args, **kwargs): 

1346 super().__init__(*args, **kwargs) 

1347 

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 

1355 

1356 

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 ) 

1396 

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 ) 

1409 

1410 

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 ) 

1452 

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 ) 

1463 

1464 def __init__(self, *args, **kwargs): 

1465 super().__init__(*args, **kwargs) 

1466 

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 

1474 

1475 

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 ) 

1531 

1532 nullable_fields = ('label', 'channels', 'channel_id', 'description', 'poe_mode', 'poe_type', 'rf_role') 

1533 

1534 

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 ) 

1558 

1559 nullable_fields = ('description',) 

1560 

1561 

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 ) 

1585 

1586 nullable_fields = ('description',) 

1587 

1588 

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 ) 

1623 

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') 

1629 

1630 

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 ) 

1650 

1651 nullable_fields = ('label', 'description') 

1652 

1653 

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 ) 

1683 

1684 nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description') 

1685 

1686 

1687# 

1688# Device components 

1689# 

1690 

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 ) 

1704 

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 

1710 

1711 super().__init__(*args, initial=initial, **kwargs) 

1712 

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 

1720 

1721 

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 ) 

1731 

1732 model = ConsolePort 

1733 fieldsets = ( 

1734 FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'), 

1735 ) 

1736 nullable_fields = ('module', 'label', 'description') 

1737 

1738 

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 ) 

1748 

1749 model = ConsoleServerPort 

1750 fieldsets = ( 

1751 FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'), 

1752 ) 

1753 nullable_fields = ('module', 'label', 'description') 

1754 

1755 

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 ) 

1765 

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') 

1772 

1773 

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 ) 

1786 

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') 

1793 

1794 def __init__(self, *args, **kwargs): 

1795 super().__init__(*args, **kwargs) 

1796 

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 

1804 

1805 

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 ) 

1818 

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 ) 

1835 

1836 

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 ) 

1858 

1859 def __init__(self, *args, **kwargs): 

1860 super().__init__(*args, **kwargs) 

1861 

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 

1869 

1870 

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 ) 

2021 

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 ) 

2048 

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') 

2056 

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 

2065 

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 ) 

2074 

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 ) 

2081 

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 

2088 

2089 def clean(self): 

2090 super().clean() 

2091 

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")}) 

2097 

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 }) 

2103 

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'] = [] 

2107 

2108 

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 ) 

2118 

2119 model = FrontPort 

2120 fieldsets = ( 

2121 FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'), 

2122 ) 

2123 nullable_fields = ('module', 'label', 'description', 'color') 

2124 

2125 

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 ) 

2135 

2136 model = RearPort 

2137 fieldsets = ( 

2138 FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'), 

2139 ) 

2140 nullable_fields = ('module', 'label', 'description', 'color') 

2141 

2142 

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 ) 

2162 

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') 

2169 

2170 

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 ) 

2180 

2181 model = DeviceBay 

2182 fieldsets = ( 

2183 FieldSet('label', 'enabled', 'description'), 

2184 ) 

2185 nullable_fields = ('label', 'description') 

2186 

2187 

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 ) 

2213 

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') 

2219 

2220 def __init__(self, *args, **kwargs): 

2221 super().__init__(*args, **kwargs) 

2222 

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) 

2226 

2227 

2228# 

2229# Device component roles 

2230# 

2231 

2232class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm): 

2233 color = ColorField( 

2234 label=_('Color'), 

2235 required=False 

2236 ) 

2237 

2238 model = InventoryItemRole 

2239 fieldsets = ( 

2240 FieldSet('color', 'description'), 

2241 ) 

2242 nullable_fields = ('color', 'description', 'comments') 

2243 

2244 

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 ) 

2261 

2262 model = VirtualDeviceContext 

2263 fieldsets = ( 

2264 FieldSet('device', 'status', 'tenant'), 

2265 ) 

2266 nullable_fields = ('device', 'tenant', ) 

2267 

2268 def __init__(self, *args, **kwargs): 

2269 super().__init__(*args, **kwargs) 

2270 

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) 

2274 

2275 

2276# 

2277# Addressing 

2278# 

2279 

2280class MACAddressBulkEditForm(PrimaryModelBulkEditForm): 

2281 model = MACAddress 

2282 fieldsets = ( 

2283 FieldSet('description'), 

2284 ) 

2285 nullable_fields = ('description', 'comments')