serializers.py 35 KB

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