serializers.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. from django.conf import settings
  2. from django.contrib.contenttypes.models import ContentType
  3. from drf_yasg.utils import swagger_serializer_method
  4. from rest_framework import serializers
  5. from rest_framework.validators import UniqueTogetherValidator
  6. from dcim.choices import *
  7. from dcim.constants import *
  8. from dcim.models import *
  9. from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
  10. from ipam.models import VLAN
  11. from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField
  12. from netbox.api.serializers import (
  13. NestedGroupModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer,
  14. WritableNestedSerializer,
  15. )
  16. from tenancy.api.nested_serializers import NestedTenantSerializer
  17. from users.api.nested_serializers import NestedUserSerializer
  18. from utilities.api import get_serializer_for_model
  19. from virtualization.api.nested_serializers import NestedClusterSerializer
  20. from .nested_serializers import *
  21. class CableTerminationSerializer(serializers.ModelSerializer):
  22. cable_peer_type = serializers.SerializerMethodField(read_only=True)
  23. cable_peer = serializers.SerializerMethodField(read_only=True)
  24. def get_cable_peer_type(self, obj):
  25. if obj._cable_peer is not None:
  26. return f'{obj._cable_peer._meta.app_label}.{obj._cable_peer._meta.model_name}'
  27. return None
  28. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  29. def get_cable_peer(self, obj):
  30. """
  31. Return the appropriate serializer for the cable termination model.
  32. """
  33. if obj._cable_peer is not None:
  34. serializer = get_serializer_for_model(obj._cable_peer, prefix='Nested')
  35. context = {'request': self.context['request']}
  36. return serializer(obj._cable_peer, context=context).data
  37. return None
  38. class ConnectedEndpointSerializer(serializers.ModelSerializer):
  39. connected_endpoint_type = serializers.SerializerMethodField(read_only=True)
  40. connected_endpoint = serializers.SerializerMethodField(read_only=True)
  41. connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
  42. def get_connected_endpoint_type(self, obj):
  43. if obj._path is not None and obj._path.destination is not None:
  44. return f'{obj._path.destination._meta.app_label}.{obj._path.destination._meta.model_name}'
  45. return None
  46. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  47. def get_connected_endpoint(self, obj):
  48. """
  49. Return the appropriate serializer for the type of connected object.
  50. """
  51. if obj._path is not None and obj._path.destination is not None:
  52. serializer = get_serializer_for_model(obj._path.destination, prefix='Nested')
  53. context = {'request': self.context['request']}
  54. return serializer(obj._path.destination, context=context).data
  55. return None
  56. @swagger_serializer_method(serializer_or_field=serializers.BooleanField)
  57. def get_connected_endpoint_reachable(self, obj):
  58. if obj._path is not None:
  59. return obj._path.is_active
  60. return None
  61. #
  62. # Regions/sites
  63. #
  64. class RegionSerializer(NestedGroupModelSerializer):
  65. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
  66. parent = NestedRegionSerializer(required=False, allow_null=True)
  67. site_count = serializers.IntegerField(read_only=True)
  68. class Meta:
  69. model = Region
  70. fields = [
  71. 'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
  72. 'site_count', '_depth',
  73. ]
  74. class SiteGroupSerializer(NestedGroupModelSerializer):
  75. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
  76. parent = NestedRegionSerializer(required=False, allow_null=True)
  77. site_count = serializers.IntegerField(read_only=True)
  78. class Meta:
  79. model = SiteGroup
  80. fields = [
  81. 'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
  82. 'site_count', '_depth',
  83. ]
  84. class SiteSerializer(PrimaryModelSerializer):
  85. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
  86. status = ChoiceField(choices=SiteStatusChoices, required=False)
  87. region = NestedRegionSerializer(required=False, allow_null=True)
  88. group = NestedSiteGroupSerializer(required=False, allow_null=True)
  89. tenant = NestedTenantSerializer(required=False, allow_null=True)
  90. time_zone = TimeZoneField(required=False)
  91. circuit_count = serializers.IntegerField(read_only=True)
  92. device_count = serializers.IntegerField(read_only=True)
  93. prefix_count = serializers.IntegerField(read_only=True)
  94. rack_count = serializers.IntegerField(read_only=True)
  95. virtualmachine_count = serializers.IntegerField(read_only=True)
  96. vlan_count = serializers.IntegerField(read_only=True)
  97. class Meta:
  98. model = Site
  99. fields = [
  100. 'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn',
  101. 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
  102. 'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
  103. 'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
  104. ]
  105. #
  106. # Racks
  107. #
  108. class LocationSerializer(NestedGroupModelSerializer):
  109. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
  110. site = NestedSiteSerializer()
  111. parent = NestedLocationSerializer(required=False, allow_null=True)
  112. rack_count = serializers.IntegerField(read_only=True)
  113. class Meta:
  114. model = Location
  115. fields = [
  116. 'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created',
  117. 'last_updated', 'rack_count', '_depth',
  118. ]
  119. class RackRoleSerializer(OrganizationalModelSerializer):
  120. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
  121. rack_count = serializers.IntegerField(read_only=True)
  122. class Meta:
  123. model = RackRole
  124. fields = [
  125. 'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'custom_fields', 'created', 'last_updated',
  126. 'rack_count',
  127. ]
  128. class RackSerializer(PrimaryModelSerializer):
  129. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
  130. site = NestedSiteSerializer()
  131. location = NestedLocationSerializer(required=False, allow_null=True, default=None)
  132. tenant = NestedTenantSerializer(required=False, allow_null=True)
  133. status = ChoiceField(choices=RackStatusChoices, required=False)
  134. role = NestedRackRoleSerializer(required=False, allow_null=True)
  135. type = ChoiceField(choices=RackTypeChoices, allow_blank=True, required=False)
  136. width = ChoiceField(choices=RackWidthChoices, required=False)
  137. outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False)
  138. device_count = serializers.IntegerField(read_only=True)
  139. powerfeed_count = serializers.IntegerField(read_only=True)
  140. class Meta:
  141. model = Rack
  142. fields = [
  143. 'id', 'url', 'display', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status',
  144. 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
  145. 'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
  146. 'powerfeed_count',
  147. ]
  148. # Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This
  149. # prevents facility_id from being interpreted as a required field.
  150. validators = [
  151. UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'name'))
  152. ]
  153. def validate(self, data):
  154. # Validate uniqueness of (location, facility_id) since we omitted the automatically-created validator from Meta.
  155. if data.get('facility_id', None):
  156. validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'facility_id'))
  157. validator(data, self)
  158. # Enforce model validation
  159. super().validate(data)
  160. return data
  161. class RackUnitSerializer(serializers.Serializer):
  162. """
  163. A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.
  164. """
  165. id = serializers.IntegerField(read_only=True)
  166. name = serializers.CharField(read_only=True)
  167. face = ChoiceField(choices=DeviceFaceChoices, read_only=True)
  168. device = NestedDeviceSerializer(read_only=True)
  169. occupied = serializers.BooleanField(read_only=True)
  170. class RackReservationSerializer(PrimaryModelSerializer):
  171. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackreservation-detail')
  172. rack = NestedRackSerializer()
  173. user = NestedUserSerializer()
  174. tenant = NestedTenantSerializer(required=False, allow_null=True)
  175. class Meta:
  176. model = RackReservation
  177. fields = [
  178. 'id', 'url', 'display', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags',
  179. 'custom_fields',
  180. ]
  181. class RackElevationDetailFilterSerializer(serializers.Serializer):
  182. q = serializers.CharField(
  183. required=False,
  184. default=None
  185. )
  186. face = serializers.ChoiceField(
  187. choices=DeviceFaceChoices,
  188. default=DeviceFaceChoices.FACE_FRONT
  189. )
  190. render = serializers.ChoiceField(
  191. choices=RackElevationDetailRenderChoices,
  192. default=RackElevationDetailRenderChoices.RENDER_JSON
  193. )
  194. unit_width = serializers.IntegerField(
  195. default=settings.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
  196. )
  197. unit_height = serializers.IntegerField(
  198. default=settings.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT
  199. )
  200. legend_width = serializers.IntegerField(
  201. default=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT
  202. )
  203. exclude = serializers.IntegerField(
  204. required=False,
  205. default=None
  206. )
  207. expand_devices = serializers.BooleanField(
  208. required=False,
  209. default=True
  210. )
  211. include_images = serializers.BooleanField(
  212. required=False,
  213. default=True
  214. )
  215. #
  216. # Device types
  217. #
  218. class ManufacturerSerializer(OrganizationalModelSerializer):
  219. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
  220. devicetype_count = serializers.IntegerField(read_only=True)
  221. inventoryitem_count = serializers.IntegerField(read_only=True)
  222. platform_count = serializers.IntegerField(read_only=True)
  223. class Meta:
  224. model = Manufacturer
  225. fields = [
  226. 'id', 'url', 'display', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated',
  227. 'devicetype_count', 'inventoryitem_count', 'platform_count',
  228. ]
  229. class DeviceTypeSerializer(PrimaryModelSerializer):
  230. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
  231. manufacturer = NestedManufacturerSerializer()
  232. subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False)
  233. device_count = serializers.IntegerField(read_only=True)
  234. class Meta:
  235. model = DeviceType
  236. fields = [
  237. 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height',
  238. 'is_full_depth', 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields',
  239. 'created', 'last_updated', 'device_count',
  240. ]
  241. class ConsolePortTemplateSerializer(ValidatedModelSerializer):
  242. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail')
  243. device_type = NestedDeviceTypeSerializer()
  244. type = ChoiceField(
  245. choices=ConsolePortTypeChoices,
  246. allow_blank=True,
  247. required=False
  248. )
  249. class Meta:
  250. model = ConsolePortTemplate
  251. fields = [
  252. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated',
  253. ]
  254. class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
  255. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverporttemplate-detail')
  256. device_type = NestedDeviceTypeSerializer()
  257. type = ChoiceField(
  258. choices=ConsolePortTypeChoices,
  259. allow_blank=True,
  260. required=False
  261. )
  262. class Meta:
  263. model = ConsoleServerPortTemplate
  264. fields = [
  265. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated',
  266. ]
  267. class PowerPortTemplateSerializer(ValidatedModelSerializer):
  268. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerporttemplate-detail')
  269. device_type = NestedDeviceTypeSerializer()
  270. type = ChoiceField(
  271. choices=PowerPortTypeChoices,
  272. allow_blank=True,
  273. required=False
  274. )
  275. class Meta:
  276. model = PowerPortTemplate
  277. fields = [
  278. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
  279. 'description', 'created', 'last_updated',
  280. ]
  281. class PowerOutletTemplateSerializer(ValidatedModelSerializer):
  282. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlettemplate-detail')
  283. device_type = NestedDeviceTypeSerializer()
  284. type = ChoiceField(
  285. choices=PowerOutletTypeChoices,
  286. allow_blank=True,
  287. required=False
  288. )
  289. power_port = NestedPowerPortTemplateSerializer(
  290. required=False
  291. )
  292. feed_leg = ChoiceField(
  293. choices=PowerOutletFeedLegChoices,
  294. allow_blank=True,
  295. required=False
  296. )
  297. class Meta:
  298. model = PowerOutletTemplate
  299. fields = [
  300. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
  301. 'created', 'last_updated',
  302. ]
  303. class InterfaceTemplateSerializer(ValidatedModelSerializer):
  304. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfacetemplate-detail')
  305. device_type = NestedDeviceTypeSerializer()
  306. type = ChoiceField(choices=InterfaceTypeChoices)
  307. class Meta:
  308. model = InterfaceTemplate
  309. fields = [
  310. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'created',
  311. 'last_updated',
  312. ]
  313. class RearPortTemplateSerializer(ValidatedModelSerializer):
  314. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearporttemplate-detail')
  315. device_type = NestedDeviceTypeSerializer()
  316. type = ChoiceField(choices=PortTypeChoices)
  317. class Meta:
  318. model = RearPortTemplate
  319. fields = [
  320. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'positions', 'description', 'created',
  321. 'last_updated',
  322. ]
  323. class FrontPortTemplateSerializer(ValidatedModelSerializer):
  324. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontporttemplate-detail')
  325. device_type = NestedDeviceTypeSerializer()
  326. type = ChoiceField(choices=PortTypeChoices)
  327. rear_port = NestedRearPortTemplateSerializer()
  328. class Meta:
  329. model = FrontPortTemplate
  330. fields = [
  331. 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position',
  332. 'description', 'created', 'last_updated',
  333. ]
  334. class DeviceBayTemplateSerializer(ValidatedModelSerializer):
  335. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
  336. device_type = NestedDeviceTypeSerializer()
  337. class Meta:
  338. model = DeviceBayTemplate
  339. fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']
  340. #
  341. # Devices
  342. #
  343. class DeviceRoleSerializer(OrganizationalModelSerializer):
  344. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
  345. device_count = serializers.IntegerField(read_only=True)
  346. virtualmachine_count = serializers.IntegerField(read_only=True)
  347. class Meta:
  348. model = DeviceRole
  349. fields = [
  350. 'id', 'url', 'display', 'name', 'slug', 'color', 'vm_role', 'description', 'custom_fields', 'created',
  351. 'last_updated', 'device_count', 'virtualmachine_count',
  352. ]
  353. class PlatformSerializer(OrganizationalModelSerializer):
  354. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
  355. manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
  356. device_count = serializers.IntegerField(read_only=True)
  357. virtualmachine_count = serializers.IntegerField(read_only=True)
  358. class Meta:
  359. model = Platform
  360. fields = [
  361. 'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description',
  362. 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
  363. ]
  364. class DeviceSerializer(PrimaryModelSerializer):
  365. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
  366. device_type = NestedDeviceTypeSerializer()
  367. device_role = NestedDeviceRoleSerializer()
  368. tenant = NestedTenantSerializer(required=False, allow_null=True)
  369. platform = NestedPlatformSerializer(required=False, allow_null=True)
  370. site = NestedSiteSerializer()
  371. location = NestedLocationSerializer(required=False, allow_null=True, default=None)
  372. rack = NestedRackSerializer(required=False, allow_null=True)
  373. face = ChoiceField(choices=DeviceFaceChoices, allow_blank=True, required=False)
  374. status = ChoiceField(choices=DeviceStatusChoices, required=False)
  375. primary_ip = NestedIPAddressSerializer(read_only=True)
  376. primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
  377. primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
  378. parent_device = serializers.SerializerMethodField()
  379. cluster = NestedClusterSerializer(required=False, allow_null=True)
  380. virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True)
  381. class Meta:
  382. model = Device
  383. fields = [
  384. 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
  385. 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
  386. 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
  387. 'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
  388. ]
  389. validators = []
  390. def validate(self, data):
  391. # Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
  392. if data.get('rack') and data.get('position') and data.get('face'):
  393. validator = UniqueTogetherValidator(queryset=Device.objects.all(), fields=('rack', 'position', 'face'))
  394. validator(data, self)
  395. # Enforce model validation
  396. super().validate(data)
  397. return data
  398. @swagger_serializer_method(serializer_or_field=NestedDeviceSerializer)
  399. def get_parent_device(self, obj):
  400. try:
  401. device_bay = obj.parent_bay
  402. except DeviceBay.DoesNotExist:
  403. return None
  404. context = {'request': self.context['request']}
  405. data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
  406. data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
  407. return data
  408. class DeviceWithConfigContextSerializer(DeviceSerializer):
  409. config_context = serializers.SerializerMethodField()
  410. class Meta(DeviceSerializer.Meta):
  411. fields = [
  412. 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
  413. 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
  414. 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
  415. 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
  416. ]
  417. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  418. def get_config_context(self, obj):
  419. return obj.get_config_context()
  420. class DeviceNAPALMSerializer(serializers.Serializer):
  421. method = serializers.DictField()
  422. #
  423. # Device components
  424. #
  425. class ConsoleServerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  426. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
  427. device = NestedDeviceSerializer()
  428. type = ChoiceField(
  429. choices=ConsolePortTypeChoices,
  430. allow_blank=True,
  431. required=False
  432. )
  433. speed = ChoiceField(
  434. choices=ConsolePortSpeedChoices,
  435. allow_blank=True,
  436. required=False
  437. )
  438. cable = NestedCableSerializer(read_only=True)
  439. class Meta:
  440. model = ConsoleServerPort
  441. fields = [
  442. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
  443. 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
  444. 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  445. ]
  446. class ConsolePortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  447. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
  448. device = NestedDeviceSerializer()
  449. type = ChoiceField(
  450. choices=ConsolePortTypeChoices,
  451. allow_blank=True,
  452. required=False
  453. )
  454. speed = ChoiceField(
  455. choices=ConsolePortSpeedChoices,
  456. allow_blank=True,
  457. required=False
  458. )
  459. cable = NestedCableSerializer(read_only=True)
  460. class Meta:
  461. model = ConsolePort
  462. fields = [
  463. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
  464. 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
  465. 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  466. ]
  467. class PowerOutletSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  468. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
  469. device = NestedDeviceSerializer()
  470. type = ChoiceField(
  471. choices=PowerOutletTypeChoices,
  472. allow_blank=True,
  473. required=False
  474. )
  475. power_port = NestedPowerPortSerializer(
  476. required=False
  477. )
  478. feed_leg = ChoiceField(
  479. choices=PowerOutletFeedLegChoices,
  480. allow_blank=True,
  481. required=False
  482. )
  483. cable = NestedCableSerializer(
  484. read_only=True
  485. )
  486. class Meta:
  487. model = PowerOutlet
  488. fields = [
  489. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
  490. 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
  491. 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  492. ]
  493. class PowerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  494. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
  495. device = NestedDeviceSerializer()
  496. type = ChoiceField(
  497. choices=PowerPortTypeChoices,
  498. allow_blank=True,
  499. required=False
  500. )
  501. cable = NestedCableSerializer(read_only=True)
  502. class Meta:
  503. model = PowerPort
  504. fields = [
  505. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
  506. 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
  507. 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  508. ]
  509. class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  510. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
  511. device = NestedDeviceSerializer()
  512. type = ChoiceField(choices=InterfaceTypeChoices)
  513. parent = NestedInterfaceSerializer(required=False, allow_null=True)
  514. lag = NestedInterfaceSerializer(required=False, allow_null=True)
  515. mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
  516. untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
  517. tagged_vlans = SerializedPKRelatedField(
  518. queryset=VLAN.objects.all(),
  519. serializer=NestedVLANSerializer,
  520. required=False,
  521. many=True
  522. )
  523. cable = NestedCableSerializer(read_only=True)
  524. count_ipaddresses = serializers.IntegerField(read_only=True)
  525. class Meta:
  526. model = Interface
  527. fields = [
  528. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address',
  529. 'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable',
  530. 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
  531. 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
  532. '_occupied',
  533. ]
  534. def validate(self, data):
  535. # Validate many-to-many VLAN assignments
  536. device = self.instance.device if self.instance else data.get('device')
  537. for vlan in data.get('tagged_vlans', []):
  538. if vlan.site not in [device.site, None]:
  539. raise serializers.ValidationError({
  540. 'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, or "
  541. f"it must be global."
  542. })
  543. return super().validate(data)
  544. class RearPortSerializer(PrimaryModelSerializer, CableTerminationSerializer):
  545. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
  546. device = NestedDeviceSerializer()
  547. type = ChoiceField(choices=PortTypeChoices)
  548. cable = NestedCableSerializer(read_only=True)
  549. class Meta:
  550. model = RearPort
  551. fields = [
  552. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected',
  553. 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  554. ]
  555. class FrontPortRearPortSerializer(WritableNestedSerializer):
  556. """
  557. NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
  558. """
  559. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
  560. class Meta:
  561. model = RearPort
  562. fields = ['id', 'url', 'display', 'name', 'label']
  563. class FrontPortSerializer(PrimaryModelSerializer, CableTerminationSerializer):
  564. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
  565. device = NestedDeviceSerializer()
  566. type = ChoiceField(choices=PortTypeChoices)
  567. rear_port = FrontPortRearPortSerializer()
  568. cable = NestedCableSerializer(read_only=True)
  569. class Meta:
  570. model = FrontPort
  571. fields = [
  572. 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description',
  573. 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created',
  574. 'last_updated', '_occupied',
  575. ]
  576. class DeviceBaySerializer(PrimaryModelSerializer):
  577. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
  578. device = NestedDeviceSerializer()
  579. installed_device = NestedDeviceSerializer(required=False, allow_null=True)
  580. class Meta:
  581. model = DeviceBay
  582. fields = [
  583. 'id', 'url', 'display', 'device', 'name', 'label', 'description', 'installed_device', 'tags',
  584. 'custom_fields', 'created', 'last_updated',
  585. ]
  586. #
  587. # Inventory items
  588. #
  589. class InventoryItemSerializer(PrimaryModelSerializer):
  590. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
  591. device = NestedDeviceSerializer()
  592. # Provide a default value to satisfy UniqueTogetherValidator
  593. parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
  594. manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
  595. _depth = serializers.IntegerField(source='level', read_only=True)
  596. class Meta:
  597. model = InventoryItem
  598. fields = [
  599. 'id', 'url', 'display', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial',
  600. 'asset_tag', 'discovered', 'description', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
  601. ]
  602. #
  603. # Cables
  604. #
  605. class CableSerializer(PrimaryModelSerializer):
  606. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
  607. termination_a_type = ContentTypeField(
  608. queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
  609. )
  610. termination_b_type = ContentTypeField(
  611. queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
  612. )
  613. termination_a = serializers.SerializerMethodField(read_only=True)
  614. termination_b = serializers.SerializerMethodField(read_only=True)
  615. status = ChoiceField(choices=CableStatusChoices, required=False)
  616. length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False)
  617. class Meta:
  618. model = Cable
  619. fields = [
  620. 'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
  621. 'termination_b_id', 'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
  622. 'custom_fields',
  623. ]
  624. def _get_termination(self, obj, side):
  625. """
  626. Serialize a nested representation of a termination.
  627. """
  628. if side.lower() not in ['a', 'b']:
  629. raise ValueError("Termination side must be either A or B.")
  630. termination = getattr(obj, 'termination_{}'.format(side.lower()))
  631. if termination is None:
  632. return None
  633. serializer = get_serializer_for_model(termination, prefix='Nested')
  634. context = {'request': self.context['request']}
  635. data = serializer(termination, context=context).data
  636. return data
  637. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  638. def get_termination_a(self, obj):
  639. return self._get_termination(obj, 'a')
  640. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  641. def get_termination_b(self, obj):
  642. return self._get_termination(obj, 'b')
  643. class TracedCableSerializer(serializers.ModelSerializer):
  644. """
  645. Used only while tracing a cable path.
  646. """
  647. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
  648. class Meta:
  649. model = Cable
  650. fields = [
  651. 'id', 'url', 'type', 'status', 'label', 'color', 'length', 'length_unit',
  652. ]
  653. class CablePathSerializer(serializers.ModelSerializer):
  654. origin_type = ContentTypeField(read_only=True)
  655. origin = serializers.SerializerMethodField(read_only=True)
  656. destination_type = ContentTypeField(read_only=True)
  657. destination = serializers.SerializerMethodField(read_only=True)
  658. path = serializers.SerializerMethodField(read_only=True)
  659. class Meta:
  660. model = CablePath
  661. fields = [
  662. 'id', 'origin_type', 'origin', 'destination_type', 'destination', 'path', 'is_active', 'is_split',
  663. ]
  664. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  665. def get_origin(self, obj):
  666. """
  667. Return the appropriate serializer for the origin.
  668. """
  669. serializer = get_serializer_for_model(obj.origin, prefix='Nested')
  670. context = {'request': self.context['request']}
  671. return serializer(obj.origin, context=context).data
  672. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  673. def get_destination(self, obj):
  674. """
  675. Return the appropriate serializer for the destination, if any.
  676. """
  677. if obj.destination_id is not None:
  678. serializer = get_serializer_for_model(obj.destination, prefix='Nested')
  679. context = {'request': self.context['request']}
  680. return serializer(obj.destination, context=context).data
  681. return None
  682. @swagger_serializer_method(serializer_or_field=serializers.ListField)
  683. def get_path(self, obj):
  684. ret = []
  685. for node in obj.get_path():
  686. serializer = get_serializer_for_model(node, prefix='Nested')
  687. context = {'request': self.context['request']}
  688. ret.append(serializer(node, context=context).data)
  689. return ret
  690. #
  691. # Interface connections
  692. #
  693. class InterfaceConnectionSerializer(ValidatedModelSerializer):
  694. interface_a = serializers.SerializerMethodField()
  695. interface_b = NestedInterfaceSerializer(source='connected_endpoint')
  696. connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
  697. class Meta:
  698. model = Interface
  699. fields = ['interface_a', 'interface_b', 'connected_endpoint_reachable']
  700. @swagger_serializer_method(serializer_or_field=NestedInterfaceSerializer)
  701. def get_interface_a(self, obj):
  702. context = {'request': self.context['request']}
  703. return NestedInterfaceSerializer(instance=obj, context=context).data
  704. @swagger_serializer_method(serializer_or_field=serializers.BooleanField)
  705. def get_connected_endpoint_reachable(self, obj):
  706. if obj._path is not None:
  707. return obj._path.is_active
  708. return None
  709. #
  710. # Virtual chassis
  711. #
  712. class VirtualChassisSerializer(PrimaryModelSerializer):
  713. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
  714. master = NestedDeviceSerializer(required=False)
  715. member_count = serializers.IntegerField(read_only=True)
  716. class Meta:
  717. model = VirtualChassis
  718. fields = ['id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
  719. #
  720. # Power panels
  721. #
  722. class PowerPanelSerializer(PrimaryModelSerializer):
  723. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
  724. site = NestedSiteSerializer()
  725. location = NestedLocationSerializer(
  726. required=False,
  727. allow_null=True,
  728. default=None
  729. )
  730. powerfeed_count = serializers.IntegerField(read_only=True)
  731. class Meta:
  732. model = PowerPanel
  733. fields = ['id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count']
  734. class PowerFeedSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
  735. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail')
  736. power_panel = NestedPowerPanelSerializer()
  737. rack = NestedRackSerializer(
  738. required=False,
  739. allow_null=True,
  740. default=None
  741. )
  742. type = ChoiceField(
  743. choices=PowerFeedTypeChoices,
  744. default=PowerFeedTypeChoices.TYPE_PRIMARY
  745. )
  746. status = ChoiceField(
  747. choices=PowerFeedStatusChoices,
  748. default=PowerFeedStatusChoices.STATUS_ACTIVE
  749. )
  750. supply = ChoiceField(
  751. choices=PowerFeedSupplyChoices,
  752. default=PowerFeedSupplyChoices.SUPPLY_AC
  753. )
  754. phase = ChoiceField(
  755. choices=PowerFeedPhaseChoices,
  756. default=PowerFeedPhaseChoices.PHASE_SINGLE
  757. )
  758. cable = NestedCableSerializer(read_only=True)
  759. class Meta:
  760. model = PowerFeed
  761. fields = [
  762. 'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
  763. 'amperage', 'max_utilization', 'comments', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type',
  764. 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
  765. 'created', 'last_updated', '_occupied',
  766. ]