serializers.py 36 KB

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