Coverage for dcim/api/serializers_/device_components.py: 89%

224 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django.contrib.contenttypes.models import ContentType 

2from django.utils.translation import gettext as _ 

3from netaddr import EUI, AddrFormatError 

4from rest_framework import serializers 

5 

6from dcim.choices import * 

7from dcim.constants import * 

8from dcim.models import ( 

9 ConsolePort, 

10 ConsoleServerPort, 

11 CoolingIntake, 

12 CoolingOutflow, 

13 DeviceBay, 

14 FrontPort, 

15 Interface, 

16 InventoryItem, 

17 ModuleBay, 

18 ModuleBayType, 

19 PortMapping, 

20 PowerOutlet, 

21 PowerPort, 

22 RearPort, 

23 VirtualDeviceContext, 

24) 

25from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer 

26from ipam.api.serializers_.vrfs import VRFSerializer 

27from ipam.models import VLAN 

28from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField 

29from netbox.api.gfk_fields import GFKSerializerField 

30from netbox.api.serializers import NetBoxModelSerializer 

31from netbox.choices import DiameterUnitChoices, FlowRateUnitChoices 

32from users.api.serializers_.mixins import OwnerMixin 

33from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer 

34from wireless.api.serializers_.nested import NestedWirelessLinkSerializer 

35from wireless.api.serializers_.wirelesslans import WirelessLANSerializer 

36from wireless.choices import * 

37from wireless.models import WirelessLAN 

38 

39from .base import ConnectedEndpointsSerializer, PortSerializer 

40from .cables import CabledObjectSerializer 

41from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer 

42from .devicetypes import ModuleBayTypeSerializer 

43from .manufacturers import ManufacturerSerializer 

44from .mixins import _UNSET, MACAddressShortcutMixin 

45from .nested import NestedCoolingOutflowSerializer, NestedInterfaceSerializer 

46from .roles import InventoryItemRoleSerializer 

47 

48__all__ = ( 

49 'ConsolePortSerializer', 

50 'ConsoleServerPortSerializer', 

51 'CoolingIntakeSerializer', 

52 'CoolingOutflowSerializer', 

53 'DeviceBaySerializer', 

54 'FrontPortSerializer', 

55 'InterfaceSerializer', 

56 'InventoryItemSerializer', 

57 'ModuleBaySerializer', 

58 'PowerOutletSerializer', 

59 'PowerPortSerializer', 

60 'RearPortSerializer', 

61) 

62 

63 

64class ConsoleServerPortSerializer( 

65 OwnerMixin, 

66 NetBoxModelSerializer, 

67 CabledObjectSerializer, 

68 ConnectedEndpointsSerializer 

69): 

70 device = DeviceSerializer(nested=True) 

71 module = ModuleSerializer( 

72 nested=True, 

73 fields=('id', 'url', 'display', 'device', 'module_bay'), 

74 required=False, 

75 allow_null=True 

76 ) 

77 type = ChoiceField( 

78 choices=ConsolePortTypeChoices, 

79 allow_blank=True, 

80 required=False 

81 ) 

82 speed = ChoiceField( 

83 choices=ConsolePortSpeedChoices, 

84 allow_null=True, 

85 required=False 

86 ) 

87 

88 class Meta: 

89 model = ConsoleServerPort 

90 fields = [ 

91 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description', 

92 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints', 

93 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created', 

94 'last_updated', '_occupied', 

95 ] 

96 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

97 

98 

99class ConsolePortSerializer( 

100 OwnerMixin, 

101 NetBoxModelSerializer, 

102 CabledObjectSerializer, 

103 ConnectedEndpointsSerializer 

104): 

105 device = DeviceSerializer(nested=True) 

106 module = ModuleSerializer( 

107 nested=True, 

108 fields=('id', 'url', 'display', 'device', 'module_bay'), 

109 required=False, 

110 allow_null=True 

111 ) 

112 type = ChoiceField( 

113 choices=ConsolePortTypeChoices, 

114 allow_blank=True, 

115 required=False 

116 ) 

117 speed = ChoiceField( 

118 choices=ConsolePortSpeedChoices, 

119 allow_null=True, 

120 required=False 

121 ) 

122 

123 class Meta: 

124 model = ConsolePort 

125 fields = [ 

126 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description', 

127 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints', 

128 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created', 

129 'last_updated', '_occupied', 

130 ] 

131 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

132 

133 

134class PowerPortSerializer( 

135 OwnerMixin, 

136 NetBoxModelSerializer, 

137 CabledObjectSerializer, 

138 ConnectedEndpointsSerializer 

139): 

140 device = DeviceSerializer(nested=True) 

141 module = ModuleSerializer( 

142 nested=True, 

143 fields=('id', 'url', 'display', 'device', 'module_bay'), 

144 required=False, 

145 allow_null=True 

146 ) 

147 type = ChoiceField( 

148 choices=PowerPortTypeChoices, 

149 allow_blank=True, 

150 required=False, 

151 allow_null=True 

152 ) 

153 

154 class Meta: 

155 model = PowerPort 

156 fields = [ 

157 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 

158 'allocated_draw', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 

159 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 

160 'custom_fields', 'created', 'last_updated', '_occupied', 

161 ] 

162 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

163 

164 

165class PowerOutletSerializer( 

166 OwnerMixin, 

167 NetBoxModelSerializer, 

168 CabledObjectSerializer, 

169 ConnectedEndpointsSerializer 

170): 

171 device = DeviceSerializer(nested=True) 

172 module = ModuleSerializer( 

173 nested=True, 

174 fields=('id', 'url', 'display', 'device', 'module_bay'), 

175 required=False, 

176 allow_null=True 

177 ) 

178 type = ChoiceField( 

179 choices=PowerOutletTypeChoices, 

180 allow_blank=True, 

181 required=False, 

182 allow_null=True 

183 ) 

184 power_port = PowerPortSerializer( 

185 nested=True, 

186 required=False, 

187 allow_null=True 

188 ) 

189 feed_leg = ChoiceField( 

190 choices=PowerOutletFeedLegChoices, 

191 allow_blank=True, 

192 required=False, 

193 allow_null=True 

194 ) 

195 status = ChoiceField(choices=PowerOutletStatusChoices, required=False) 

196 

197 class Meta: 

198 model = PowerOutlet 

199 fields = [ 

200 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'status', 'color', 

201 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 

202 'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 

203 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', 

204 ] 

205 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

206 

207 

208class CoolingIntakeSerializer(OwnerMixin, NetBoxModelSerializer): 

209 device = DeviceSerializer(nested=True) 

210 module = ModuleSerializer( 

211 nested=True, 

212 fields=('id', 'url', 'display', 'device', 'module_bay'), 

213 required=False, 

214 allow_null=True 

215 ) 

216 type = ChoiceField( 

217 choices=CoolingConnectorTypeChoices, 

218 allow_blank=True, 

219 required=False, 

220 allow_null=True 

221 ) 

222 diameter_unit = ChoiceField( 

223 choices=DiameterUnitChoices, 

224 allow_blank=True, 

225 required=False, 

226 allow_null=True 

227 ) 

228 max_flow_unit = ChoiceField( 

229 choices=FlowRateUnitChoices, 

230 allow_blank=True, 

231 required=False, 

232 allow_null=True 

233 ) 

234 cooling_outflow = NestedCoolingOutflowSerializer( 

235 required=False, 

236 allow_null=True 

237 ) 

238 

239 class Meta: 

240 model = CoolingIntake 

241 fields = [ 

242 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 

243 'diameter', 'diameter_unit', 'max_flow', 'max_flow_unit', 'cooling_outflow', 

244 'description', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', 

245 ] 

246 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description') 

247 

248 

249class CoolingOutflowSerializer(OwnerMixin, NetBoxModelSerializer): 

250 device = DeviceSerializer(nested=True) 

251 module = ModuleSerializer( 

252 nested=True, 

253 fields=('id', 'url', 'display', 'device', 'module_bay'), 

254 required=False, 

255 allow_null=True 

256 ) 

257 type = ChoiceField( 

258 choices=CoolingConnectorTypeChoices, 

259 allow_blank=True, 

260 required=False, 

261 allow_null=True 

262 ) 

263 diameter_unit = ChoiceField( 

264 choices=DiameterUnitChoices, 

265 allow_blank=True, 

266 required=False, 

267 allow_null=True 

268 ) 

269 cooling_intake = CoolingIntakeSerializer( 

270 nested=True, 

271 required=False, 

272 allow_null=True 

273 ) 

274 

275 class Meta: 

276 model = CoolingOutflow 

277 fields = [ 

278 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 

279 'diameter', 'diameter_unit', 'cooling_intake', 'description', 'owner', 'tags', 'custom_fields', 

280 'created', 'last_updated', 

281 ] 

282 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description') 

283 

284 

285class InterfaceSerializer( 

286 MACAddressShortcutMixin, 

287 OwnerMixin, 

288 NetBoxModelSerializer, 

289 CabledObjectSerializer, 

290 ConnectedEndpointsSerializer 

291): 

292 device = DeviceSerializer(nested=True) 

293 vdcs = SerializedPKRelatedField( 

294 queryset=VirtualDeviceContext.objects.all(), 

295 serializer=VirtualDeviceContextSerializer, 

296 nested=True, 

297 required=False, 

298 many=True 

299 ) 

300 module = ModuleSerializer( 

301 nested=True, 

302 fields=('id', 'url', 'display', 'device', 'module_bay'), 

303 required=False, 

304 allow_null=True 

305 ) 

306 type = ChoiceField(choices=InterfaceTypeChoices) 

307 parent = NestedInterfaceSerializer(required=False, allow_null=True) 

308 bridge = NestedInterfaceSerializer(required=False, allow_null=True) 

309 bridge_interfaces = NestedInterfaceSerializer(many=True, read_only=True) 

310 lag = NestedInterfaceSerializer(required=False, allow_null=True) 

311 mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True) 

312 duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True, allow_null=True) 

313 rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True) 

314 rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True) 

315 poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True) 

316 poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True) 

317 untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True) 

318 tagged_vlans = SerializedPKRelatedField( 

319 queryset=VLAN.objects.all(), 

320 serializer=VLANSerializer, 

321 nested=True, 

322 required=False, 

323 many=True 

324 ) 

325 qinq_svlan = VLANSerializer(nested=True, required=False, allow_null=True) 

326 vlan_translation_policy = VLANTranslationPolicySerializer(nested=True, required=False, allow_null=True) 

327 vrf = VRFSerializer(nested=True, required=False, allow_null=True) 

328 l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True) 

329 wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True) 

330 wireless_lans = SerializedPKRelatedField( 

331 queryset=WirelessLAN.objects.all(), 

332 serializer=WirelessLANSerializer, 

333 nested=True, 

334 required=False, 

335 many=True 

336 ) 

337 count_ipaddresses = serializers.IntegerField(read_only=True) 

338 count_fhrp_groups = serializers.IntegerField(read_only=True) 

339 # Maintains backward compatibility with NetBox <v4.2; also accepts a MAC string on write to 

340 # create/update the primary MAC address in a single request. 

341 mac_address = serializers.CharField(allow_null=True, required=False) 

342 primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True) 

343 mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True) 

344 wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True) 

345 

346 class Meta: 

347 model = Interface 

348 fields = [ 

349 'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'channels', 

350 'channel_id', 'enabled', 

351 'parent', 'bridge', 'bridge_interfaces', 'lag', 'mtu', 'mac_address', 'primary_mac_address', 

352 'mac_addresses', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 

353 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 

354 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 

355 'wireless_link', 'link_peers', 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 

356 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 

357 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', 

358 ] 

359 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

360 

361 def validate(self, data): 

362 # Pop mac_address before model validation — it's a cached_property, not a model field, 

363 # and passing it to Interface(**attrs) in ValidatedModelSerializer.validate() would raise TypeError. 

364 # data may be an Interface instance (not a dict) in some custom field code paths (#18887). 

365 mac_address = _UNSET 

366 if isinstance(data, dict): 366 ↛ 368line 366 didn't jump to line 368 because the condition on line 366 was always true

367 mac_address = data.pop('mac_address', _UNSET) 

368 self._validate_no_mac_conflict(data, mac_address) 

369 

370 if not self.nested and isinstance(data, dict): 370 ↛ 435line 370 didn't jump to line 435 because the condition on line 370 was always true

371 if mac_address not in (_UNSET, None): 371 ↛ 372line 371 didn't jump to line 372 because the condition on line 371 was never true

372 try: 

373 EUI(mac_address, version=48) 

374 except (AddrFormatError, ValueError, TypeError): 

375 raise serializers.ValidationError({ 

376 'mac_address': _('Enter a valid MAC address (e.g. 00:11:22:33:44:55).') 

377 }) 

378 

379 # Validate 802.1q mode and vlan(s) 

380 mode = None 

381 tagged_vlans = [] 

382 

383 # Gather Information 

384 if self.instance: 

385 mode = data.get('mode') if 'mode' in data.keys() else self.instance.mode 

386 untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else \ 

387 self.instance.untagged_vlan 

388 qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else \ 

389 self.instance.qinq_svlan 

390 tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else \ 

391 self.instance.tagged_vlans.all() 

392 else: 

393 mode = data.get('mode', None) 

394 untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else None 

395 qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else None 

396 tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else None 

397 

398 errors = {} 

399 

400 # Non Q-in-Q mode with service vlan set 

401 if mode != InterfaceModeChoices.MODE_Q_IN_Q and qinq_svlan: 401 ↛ 402line 401 didn't jump to line 402 because the condition on line 401 was never true

402 errors.update({ 

403 'qinq_svlan': _("Interface mode does not support q-in-q service vlan") 

404 }) 

405 # Routed mode 

406 if not mode: 406 ↛ 418line 406 didn't jump to line 418 because the condition on line 406 was always true

407 # Untagged vlan 

408 if untagged_vlan: 408 ↛ 409line 408 didn't jump to line 409 because the condition on line 408 was never true

409 errors.update({ 

410 'untagged_vlan': _("Interface mode does not support untagged vlan") 

411 }) 

412 # Tagged vlan 

413 if tagged_vlans: 413 ↛ 414line 413 didn't jump to line 414 because the condition on line 413 was never true

414 errors.update({ 

415 'tagged_vlans': _("Interface mode does not support tagged vlans") 

416 }) 

417 # Non-tagged mode 

418 elif mode in (InterfaceModeChoices.MODE_TAGGED_ALL, InterfaceModeChoices.MODE_ACCESS) and tagged_vlans: 

419 errors.update({ 

420 'tagged_vlans': _("Interface mode does not support tagged vlans") 

421 }) 

422 

423 if errors: 423 ↛ 424line 423 didn't jump to line 424 because the condition on line 423 was never true

424 raise serializers.ValidationError(errors) 

425 

426 # Validate many-to-many VLAN assignments 

427 device = self.instance.device if self.instance else data.get('device') 

428 for vlan in data.get('tagged_vlans', []): 428 ↛ 429line 428 didn't jump to line 429 because the loop on line 428 never started

429 if vlan.site not in [device.site, None]: 

430 raise serializers.ValidationError({ 

431 'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, " 

432 f"or it must be global." 

433 }) 

434 

435 data = super().validate(data) 

436 

437 if mac_address is not _UNSET: 437 ↛ 438line 437 didn't jump to line 438 because the condition on line 437 was never true

438 data['mac_address'] = mac_address 

439 

440 return data 

441 

442 

443class RearPortMappingSerializer(serializers.ModelSerializer): 

444 position = serializers.IntegerField( 

445 source='rear_port_position' 

446 ) 

447 front_port = serializers.PrimaryKeyRelatedField( 

448 queryset=FrontPort.objects.all(), 

449 ) 

450 

451 class Meta: 

452 model = PortMapping 

453 fields = ('position', 'front_port', 'front_port_position') 

454 

455 

456class RearPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer): 

457 device = DeviceSerializer(nested=True) 

458 module = ModuleSerializer( 

459 nested=True, 

460 fields=('id', 'url', 'display', 'device', 'module_bay'), 

461 required=False, 

462 allow_null=True 

463 ) 

464 type = ChoiceField(choices=PortTypeChoices) 

465 front_ports = RearPortMappingSerializer( 

466 source='mappings', 

467 many=True, 

468 required=False, 

469 ) 

470 

471 class Meta: 

472 model = RearPort 

473 fields = [ 

474 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 

475 'front_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 

476 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', 

477 ] 

478 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

479 

480 

481class FrontPortMappingSerializer(serializers.ModelSerializer): 

482 position = serializers.IntegerField( 

483 source='front_port_position' 

484 ) 

485 rear_port = serializers.PrimaryKeyRelatedField( 

486 queryset=RearPort.objects.all(), 

487 ) 

488 

489 class Meta: 

490 model = PortMapping 

491 fields = ('position', 'rear_port', 'rear_port_position') 

492 

493 

494class FrontPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer): 

495 device = DeviceSerializer(nested=True) 

496 module = ModuleSerializer( 

497 nested=True, 

498 fields=('id', 'url', 'display', 'device', 'module_bay'), 

499 required=False, 

500 allow_null=True 

501 ) 

502 type = ChoiceField(choices=PortTypeChoices) 

503 rear_ports = FrontPortMappingSerializer( 

504 source='mappings', 

505 many=True, 

506 required=False, 

507 ) 

508 

509 class Meta: 

510 model = FrontPort 

511 fields = [ 

512 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 

513 'rear_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 

514 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', 

515 ] 

516 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied') 

517 

518 

519class ModuleBaySerializer(OwnerMixin, NetBoxModelSerializer): 

520 device = DeviceSerializer(nested=True) 

521 module = ModuleSerializer( 

522 nested=True, 

523 fields=('id', 'url', 'display'), 

524 required=False, 

525 allow_null=True, 

526 default=None 

527 ) 

528 installed_module = ModuleSerializer( 

529 nested=True, 

530 fields=('id', 'url', 'display', 'serial', 'description'), 

531 required=False, 

532 allow_null=True 

533 ) 

534 module_bay_types = SerializedPKRelatedField( 

535 queryset=ModuleBayType.objects.all(), 

536 serializer=ModuleBayTypeSerializer, 

537 nested=True, 

538 required=False, 

539 many=True 

540 ) 

541 _occupied = serializers.BooleanField(required=False, read_only=True) 

542 is_module_compatible = serializers.BooleanField(read_only=True) 

543 

544 class Meta: 

545 model = ModuleBay 

546 fields = [ 

547 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'position', 'enabled', 

548 'description', 'module_bay_types', 'installed_module', 'owner', 'tags', 'custom_fields', 'created', 

549 'last_updated', '_occupied', 'is_module_compatible', 

550 ] 

551 brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'enabled', 'description', '_occupied') 

552 

553 

554class DeviceBaySerializer(OwnerMixin, NetBoxModelSerializer): 

555 device = DeviceSerializer(nested=True) 

556 installed_device = DeviceSerializer(nested=True, required=False, allow_null=True) 

557 _occupied = serializers.BooleanField(required=False, read_only=True) 

558 

559 class Meta: 

560 model = DeviceBay 

561 fields = [ 

562 'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'enabled', 'description', 

563 'installed_device', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', 

564 ] 

565 brief_fields = ('id', 'url', 'display', 'device', 'name', 'enabled', 'description', '_occupied',) 

566 

567 

568class InventoryItemSerializer(OwnerMixin, NetBoxModelSerializer): 

569 device = DeviceSerializer(nested=True) 

570 parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None) 

571 role = InventoryItemRoleSerializer(nested=True, required=False, allow_null=True) 

572 manufacturer = ManufacturerSerializer(nested=True, required=False, allow_null=True, default=None) 

573 component_type = ContentTypeField( 

574 queryset=ContentType.objects.filter(MODULAR_COMPONENT_MODELS), 

575 required=False, 

576 allow_null=True 

577 ) 

578 component = GFKSerializerField(read_only=True) 

579 _depth = serializers.IntegerField(source='level', read_only=True) 

580 status = ChoiceField(choices=InventoryItemStatusChoices, required=False) 

581 

582 class Meta: 

583 model = InventoryItem 

584 fields = [ 

585 'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role', 

586 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 

587 'component_id', 'component', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_depth', 

588 ] 

589 brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')