device_components.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.utils.translation import gettext as _
  3. from netaddr import EUI, AddrFormatError
  4. from rest_framework import serializers
  5. from dcim.choices import *
  6. from dcim.constants import *
  7. from dcim.models import (
  8. ConsolePort,
  9. ConsoleServerPort,
  10. CoolingIntake,
  11. CoolingOutflow,
  12. DeviceBay,
  13. FrontPort,
  14. Interface,
  15. InventoryItem,
  16. ModuleBay,
  17. ModuleBayType,
  18. PortMapping,
  19. PowerOutlet,
  20. PowerPort,
  21. RearPort,
  22. VirtualDeviceContext,
  23. )
  24. from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer
  25. from ipam.api.serializers_.vrfs import VRFSerializer
  26. from ipam.models import VLAN
  27. from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
  28. from netbox.api.gfk_fields import GFKSerializerField
  29. from netbox.api.serializers import NetBoxModelSerializer
  30. from netbox.choices import DiameterUnitChoices, FlowRateUnitChoices
  31. from users.api.serializers_.mixins import OwnerMixin
  32. from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
  33. from wireless.api.serializers_.nested import NestedWirelessLinkSerializer
  34. from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
  35. from wireless.choices import *
  36. from wireless.models import WirelessLAN
  37. from .base import ConnectedEndpointsSerializer, PortSerializer
  38. from .cables import CabledObjectSerializer
  39. from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer
  40. from .devicetypes import ModuleBayTypeSerializer
  41. from .manufacturers import ManufacturerSerializer
  42. from .mixins import _UNSET, MACAddressShortcutMixin
  43. from .nested import NestedCoolingOutflowSerializer, NestedInterfaceSerializer
  44. from .roles import InventoryItemRoleSerializer
  45. __all__ = (
  46. 'ConsolePortSerializer',
  47. 'ConsoleServerPortSerializer',
  48. 'CoolingIntakeSerializer',
  49. 'CoolingOutflowSerializer',
  50. 'DeviceBaySerializer',
  51. 'FrontPortSerializer',
  52. 'InterfaceSerializer',
  53. 'InventoryItemSerializer',
  54. 'ModuleBaySerializer',
  55. 'PowerOutletSerializer',
  56. 'PowerPortSerializer',
  57. 'RearPortSerializer',
  58. )
  59. class ConsoleServerPortSerializer(
  60. OwnerMixin,
  61. NetBoxModelSerializer,
  62. CabledObjectSerializer,
  63. ConnectedEndpointsSerializer
  64. ):
  65. device = DeviceSerializer(nested=True)
  66. module = ModuleSerializer(
  67. nested=True,
  68. fields=('id', 'url', 'display', 'device', 'module_bay'),
  69. required=False,
  70. allow_null=True
  71. )
  72. type = ChoiceField(
  73. choices=ConsolePortTypeChoices,
  74. allow_blank=True,
  75. required=False
  76. )
  77. speed = ChoiceField(
  78. choices=ConsolePortSpeedChoices,
  79. allow_null=True,
  80. required=False
  81. )
  82. class Meta:
  83. model = ConsoleServerPort
  84. fields = [
  85. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
  86. 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
  87. 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created',
  88. 'last_updated', '_occupied',
  89. ]
  90. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  91. class ConsolePortSerializer(
  92. OwnerMixin,
  93. NetBoxModelSerializer,
  94. CabledObjectSerializer,
  95. ConnectedEndpointsSerializer
  96. ):
  97. device = DeviceSerializer(nested=True)
  98. module = ModuleSerializer(
  99. nested=True,
  100. fields=('id', 'url', 'display', 'device', 'module_bay'),
  101. required=False,
  102. allow_null=True
  103. )
  104. type = ChoiceField(
  105. choices=ConsolePortTypeChoices,
  106. allow_blank=True,
  107. required=False
  108. )
  109. speed = ChoiceField(
  110. choices=ConsolePortSpeedChoices,
  111. allow_null=True,
  112. required=False
  113. )
  114. class Meta:
  115. model = ConsolePort
  116. fields = [
  117. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
  118. 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
  119. 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags', 'custom_fields', 'created',
  120. 'last_updated', '_occupied',
  121. ]
  122. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  123. class PowerPortSerializer(
  124. OwnerMixin,
  125. NetBoxModelSerializer,
  126. CabledObjectSerializer,
  127. ConnectedEndpointsSerializer
  128. ):
  129. device = DeviceSerializer(nested=True)
  130. module = ModuleSerializer(
  131. nested=True,
  132. fields=('id', 'url', 'display', 'device', 'module_bay'),
  133. required=False,
  134. allow_null=True
  135. )
  136. type = ChoiceField(
  137. choices=PowerPortTypeChoices,
  138. allow_blank=True,
  139. required=False,
  140. allow_null=True
  141. )
  142. class Meta:
  143. model = PowerPort
  144. fields = [
  145. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw',
  146. 'allocated_draw', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
  147. 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags',
  148. 'custom_fields', 'created', 'last_updated', '_occupied',
  149. ]
  150. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  151. class PowerOutletSerializer(
  152. OwnerMixin,
  153. NetBoxModelSerializer,
  154. CabledObjectSerializer,
  155. ConnectedEndpointsSerializer
  156. ):
  157. device = DeviceSerializer(nested=True)
  158. module = ModuleSerializer(
  159. nested=True,
  160. fields=('id', 'url', 'display', 'device', 'module_bay'),
  161. required=False,
  162. allow_null=True
  163. )
  164. type = ChoiceField(
  165. choices=PowerOutletTypeChoices,
  166. allow_blank=True,
  167. required=False,
  168. allow_null=True
  169. )
  170. power_port = PowerPortSerializer(
  171. nested=True,
  172. required=False,
  173. allow_null=True
  174. )
  175. feed_leg = ChoiceField(
  176. choices=PowerOutletFeedLegChoices,
  177. allow_blank=True,
  178. required=False,
  179. allow_null=True
  180. )
  181. status = ChoiceField(choices=PowerOutletStatusChoices, required=False)
  182. class Meta:
  183. model = PowerOutlet
  184. fields = [
  185. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'status', 'color',
  186. 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers',
  187. 'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
  188. 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  189. ]
  190. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  191. class CoolingIntakeSerializer(OwnerMixin, NetBoxModelSerializer):
  192. device = DeviceSerializer(nested=True)
  193. module = ModuleSerializer(
  194. nested=True,
  195. fields=('id', 'url', 'display', 'device', 'module_bay'),
  196. required=False,
  197. allow_null=True
  198. )
  199. type = ChoiceField(
  200. choices=CoolingConnectorTypeChoices,
  201. allow_blank=True,
  202. required=False,
  203. allow_null=True
  204. )
  205. diameter_unit = ChoiceField(
  206. choices=DiameterUnitChoices,
  207. allow_blank=True,
  208. required=False,
  209. allow_null=True
  210. )
  211. max_flow_unit = ChoiceField(
  212. choices=FlowRateUnitChoices,
  213. allow_blank=True,
  214. required=False,
  215. allow_null=True
  216. )
  217. cooling_outflow = NestedCoolingOutflowSerializer(
  218. required=False,
  219. allow_null=True
  220. )
  221. class Meta:
  222. model = CoolingIntake
  223. fields = [
  224. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type',
  225. 'diameter', 'diameter_unit', 'max_flow', 'max_flow_unit', 'cooling_outflow',
  226. 'description', 'owner', 'tags', 'custom_fields', 'created', 'last_updated',
  227. ]
  228. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
  229. class CoolingOutflowSerializer(OwnerMixin, NetBoxModelSerializer):
  230. device = DeviceSerializer(nested=True)
  231. module = ModuleSerializer(
  232. nested=True,
  233. fields=('id', 'url', 'display', 'device', 'module_bay'),
  234. required=False,
  235. allow_null=True
  236. )
  237. type = ChoiceField(
  238. choices=CoolingConnectorTypeChoices,
  239. allow_blank=True,
  240. required=False,
  241. allow_null=True
  242. )
  243. diameter_unit = ChoiceField(
  244. choices=DiameterUnitChoices,
  245. allow_blank=True,
  246. required=False,
  247. allow_null=True
  248. )
  249. cooling_intake = CoolingIntakeSerializer(
  250. nested=True,
  251. required=False,
  252. allow_null=True
  253. )
  254. class Meta:
  255. model = CoolingOutflow
  256. fields = [
  257. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type',
  258. 'diameter', 'diameter_unit', 'cooling_intake', 'description', 'owner', 'tags', 'custom_fields',
  259. 'created', 'last_updated',
  260. ]
  261. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
  262. class InterfaceSerializer(
  263. MACAddressShortcutMixin,
  264. OwnerMixin,
  265. NetBoxModelSerializer,
  266. CabledObjectSerializer,
  267. ConnectedEndpointsSerializer
  268. ):
  269. device = DeviceSerializer(nested=True)
  270. vdcs = SerializedPKRelatedField(
  271. queryset=VirtualDeviceContext.objects.all(),
  272. serializer=VirtualDeviceContextSerializer,
  273. nested=True,
  274. required=False,
  275. many=True
  276. )
  277. module = ModuleSerializer(
  278. nested=True,
  279. fields=('id', 'url', 'display', 'device', 'module_bay'),
  280. required=False,
  281. allow_null=True
  282. )
  283. type = ChoiceField(choices=InterfaceTypeChoices)
  284. parent = NestedInterfaceSerializer(required=False, allow_null=True)
  285. bridge = NestedInterfaceSerializer(required=False, allow_null=True)
  286. bridge_interfaces = NestedInterfaceSerializer(many=True, read_only=True)
  287. lag = NestedInterfaceSerializer(required=False, allow_null=True)
  288. mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True)
  289. duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True, allow_null=True)
  290. rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True)
  291. rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True)
  292. poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True)
  293. poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True)
  294. untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True)
  295. tagged_vlans = SerializedPKRelatedField(
  296. queryset=VLAN.objects.all(),
  297. serializer=VLANSerializer,
  298. nested=True,
  299. required=False,
  300. many=True
  301. )
  302. qinq_svlan = VLANSerializer(nested=True, required=False, allow_null=True)
  303. vlan_translation_policy = VLANTranslationPolicySerializer(nested=True, required=False, allow_null=True)
  304. vrf = VRFSerializer(nested=True, required=False, allow_null=True)
  305. l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True)
  306. wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True)
  307. wireless_lans = SerializedPKRelatedField(
  308. queryset=WirelessLAN.objects.all(),
  309. serializer=WirelessLANSerializer,
  310. nested=True,
  311. required=False,
  312. many=True
  313. )
  314. count_ipaddresses = serializers.IntegerField(read_only=True)
  315. count_fhrp_groups = serializers.IntegerField(read_only=True)
  316. # Maintains backward compatibility with NetBox <v4.2; also accepts a MAC string on write to
  317. # create/update the primary MAC address in a single request.
  318. mac_address = serializers.CharField(allow_null=True, required=False)
  319. primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True)
  320. mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True)
  321. wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
  322. class Meta:
  323. model = Interface
  324. fields = [
  325. 'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'channels',
  326. 'channel_id', 'enabled',
  327. 'parent', 'bridge', 'bridge_interfaces', 'lag', 'mtu', 'mac_address', 'primary_mac_address',
  328. 'mac_addresses', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
  329. 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
  330. 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end',
  331. 'wireless_link', 'link_peers', 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination',
  332. 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'owner', 'tags',
  333. 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
  334. ]
  335. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  336. def validate(self, data):
  337. # Pop mac_address before model validation — it's a cached_property, not a model field,
  338. # and passing it to Interface(**attrs) in ValidatedModelSerializer.validate() would raise TypeError.
  339. # data may be an Interface instance (not a dict) in some custom field code paths (#18887).
  340. mac_address = _UNSET
  341. if isinstance(data, dict):
  342. mac_address = data.pop('mac_address', _UNSET)
  343. self._validate_no_mac_conflict(data, mac_address)
  344. if not self.nested and isinstance(data, dict):
  345. if mac_address not in (_UNSET, None):
  346. try:
  347. EUI(mac_address, version=48)
  348. except (AddrFormatError, ValueError, TypeError):
  349. raise serializers.ValidationError({
  350. 'mac_address': _('Enter a valid MAC address (e.g. 00:11:22:33:44:55).')
  351. })
  352. # Validate 802.1q mode and vlan(s)
  353. mode = None
  354. tagged_vlans = []
  355. # Gather Information
  356. if self.instance:
  357. mode = data.get('mode') if 'mode' in data.keys() else self.instance.mode
  358. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else \
  359. self.instance.untagged_vlan
  360. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else \
  361. self.instance.qinq_svlan
  362. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else \
  363. self.instance.tagged_vlans.all()
  364. else:
  365. mode = data.get('mode', None)
  366. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else None
  367. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else None
  368. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else None
  369. errors = {}
  370. # Non Q-in-Q mode with service vlan set
  371. if mode != InterfaceModeChoices.MODE_Q_IN_Q and qinq_svlan:
  372. errors.update({
  373. 'qinq_svlan': _("Interface mode does not support q-in-q service vlan")
  374. })
  375. # Routed mode
  376. if not mode:
  377. # Untagged vlan
  378. if untagged_vlan:
  379. errors.update({
  380. 'untagged_vlan': _("Interface mode does not support untagged vlan")
  381. })
  382. # Tagged vlan
  383. if tagged_vlans:
  384. errors.update({
  385. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  386. })
  387. # Non-tagged mode
  388. elif mode in (InterfaceModeChoices.MODE_TAGGED_ALL, InterfaceModeChoices.MODE_ACCESS) and tagged_vlans:
  389. errors.update({
  390. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  391. })
  392. if errors:
  393. raise serializers.ValidationError(errors)
  394. # Validate many-to-many VLAN assignments
  395. device = self.instance.device if self.instance else data.get('device')
  396. for vlan in data.get('tagged_vlans', []):
  397. if vlan.site not in [device.site, None]:
  398. raise serializers.ValidationError({
  399. 'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, "
  400. f"or it must be global."
  401. })
  402. data = super().validate(data)
  403. if mac_address is not _UNSET:
  404. data['mac_address'] = mac_address
  405. return data
  406. class RearPortMappingSerializer(serializers.ModelSerializer):
  407. position = serializers.IntegerField(
  408. source='rear_port_position'
  409. )
  410. front_port = serializers.PrimaryKeyRelatedField(
  411. queryset=FrontPort.objects.all(),
  412. )
  413. class Meta:
  414. model = PortMapping
  415. fields = ('position', 'front_port', 'front_port_position')
  416. class RearPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
  417. device = DeviceSerializer(nested=True)
  418. module = ModuleSerializer(
  419. nested=True,
  420. fields=('id', 'url', 'display', 'device', 'module_bay'),
  421. required=False,
  422. allow_null=True
  423. )
  424. type = ChoiceField(choices=PortTypeChoices)
  425. front_ports = RearPortMappingSerializer(
  426. source='mappings',
  427. many=True,
  428. required=False,
  429. )
  430. class Meta:
  431. model = RearPort
  432. fields = [
  433. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
  434. 'front_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
  435. 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  436. ]
  437. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  438. class FrontPortMappingSerializer(serializers.ModelSerializer):
  439. position = serializers.IntegerField(
  440. source='front_port_position'
  441. )
  442. rear_port = serializers.PrimaryKeyRelatedField(
  443. queryset=RearPort.objects.all(),
  444. )
  445. class Meta:
  446. model = PortMapping
  447. fields = ('position', 'rear_port', 'rear_port_position')
  448. class FrontPortSerializer(OwnerMixin, NetBoxModelSerializer, CabledObjectSerializer, PortSerializer):
  449. device = DeviceSerializer(nested=True)
  450. module = ModuleSerializer(
  451. nested=True,
  452. fields=('id', 'url', 'display', 'device', 'module_bay'),
  453. required=False,
  454. allow_null=True
  455. )
  456. type = ChoiceField(choices=PortTypeChoices)
  457. rear_ports = FrontPortMappingSerializer(
  458. source='mappings',
  459. many=True,
  460. required=False,
  461. )
  462. class Meta:
  463. model = FrontPort
  464. fields = [
  465. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
  466. 'rear_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
  467. 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  468. ]
  469. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  470. class ModuleBaySerializer(OwnerMixin, NetBoxModelSerializer):
  471. device = DeviceSerializer(nested=True)
  472. module = ModuleSerializer(
  473. nested=True,
  474. fields=('id', 'url', 'display'),
  475. required=False,
  476. allow_null=True,
  477. default=None
  478. )
  479. installed_module = ModuleSerializer(
  480. nested=True,
  481. fields=('id', 'url', 'display', 'serial', 'description'),
  482. read_only=True,
  483. allow_null=True
  484. )
  485. module_bay_types = SerializedPKRelatedField(
  486. queryset=ModuleBayType.objects.all(),
  487. serializer=ModuleBayTypeSerializer,
  488. nested=True,
  489. required=False,
  490. many=True
  491. )
  492. _occupied = serializers.BooleanField(required=False, read_only=True)
  493. is_module_compatible = serializers.BooleanField(read_only=True)
  494. class Meta:
  495. model = ModuleBay
  496. fields = [
  497. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'position', 'enabled',
  498. 'description', 'module_bay_types', 'installed_module', 'owner', 'tags', 'custom_fields', 'created',
  499. 'last_updated', '_occupied', 'is_module_compatible',
  500. ]
  501. brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'enabled', 'description', '_occupied')
  502. class DeviceBaySerializer(OwnerMixin, NetBoxModelSerializer):
  503. device = DeviceSerializer(nested=True)
  504. installed_device = DeviceSerializer(nested=True, required=False, allow_null=True)
  505. _occupied = serializers.BooleanField(required=False, read_only=True)
  506. class Meta:
  507. model = DeviceBay
  508. fields = [
  509. 'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'enabled', 'description',
  510. 'installed_device', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  511. ]
  512. brief_fields = ('id', 'url', 'display', 'device', 'name', 'enabled', 'description', '_occupied',)
  513. class InventoryItemSerializer(OwnerMixin, NetBoxModelSerializer):
  514. device = DeviceSerializer(nested=True)
  515. parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
  516. role = InventoryItemRoleSerializer(nested=True, required=False, allow_null=True)
  517. manufacturer = ManufacturerSerializer(nested=True, required=False, allow_null=True, default=None)
  518. component_type = ContentTypeField(
  519. queryset=ContentType.objects.filter(MODULAR_COMPONENT_MODELS),
  520. required=False,
  521. allow_null=True
  522. )
  523. component = GFKSerializerField(read_only=True)
  524. _depth = serializers.IntegerField(source='level', read_only=True)
  525. status = ChoiceField(choices=InventoryItemStatusChoices, required=False)
  526. class Meta:
  527. model = InventoryItem
  528. fields = [
  529. 'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
  530. 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
  531. 'component_id', 'component', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
  532. ]
  533. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')