serializers.py 32 KB

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