serializers.py 36 KB

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