serializers.py 30 KB

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