serializers.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. from __future__ import unicode_literals
  2. from rest_framework import serializers
  3. from rest_framework.validators import UniqueTogetherValidator
  4. from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
  5. from circuits.models import Circuit, CircuitTermination
  6. from dcim.constants import (
  7. CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
  8. RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
  9. )
  10. from dcim.models import (
  11. ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
  12. DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
  13. InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
  14. RackReservation, RackRole, Region, Site, VirtualChassis,
  15. )
  16. from extras.api.customfields import CustomFieldModelSerializer
  17. from ipam.models import IPAddress, VLAN
  18. from tenancy.api.serializers import NestedTenantSerializer
  19. from users.api.serializers import NestedUserSerializer
  20. from utilities.api import (
  21. ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
  22. WritableNestedSerializer,
  23. )
  24. from virtualization.models import Cluster
  25. #
  26. # Regions
  27. #
  28. class NestedRegionSerializer(WritableNestedSerializer):
  29. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
  30. class Meta:
  31. model = Region
  32. fields = ['id', 'url', 'name', 'slug']
  33. class RegionSerializer(serializers.ModelSerializer):
  34. parent = NestedRegionSerializer(required=False, allow_null=True)
  35. class Meta:
  36. model = Region
  37. fields = ['id', 'name', 'slug', 'parent']
  38. #
  39. # Sites
  40. #
  41. class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
  42. status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
  43. region = NestedRegionSerializer(required=False, allow_null=True)
  44. tenant = NestedTenantSerializer(required=False, allow_null=True)
  45. time_zone = TimeZoneField(required=False)
  46. tags = TagListSerializerField(required=False)
  47. count_prefixes = serializers.IntegerField(read_only=True)
  48. count_vlans = serializers.IntegerField(read_only=True)
  49. count_racks = serializers.IntegerField(read_only=True)
  50. count_devices = serializers.IntegerField(read_only=True)
  51. count_circuits = serializers.IntegerField(read_only=True)
  52. class Meta:
  53. model = Site
  54. fields = [
  55. 'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
  56. 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
  57. 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'count_prefixes',
  58. 'count_vlans', 'count_racks', 'count_devices', 'count_circuits',
  59. ]
  60. class NestedSiteSerializer(WritableNestedSerializer):
  61. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
  62. class Meta:
  63. model = Site
  64. fields = ['id', 'url', 'name', 'slug']
  65. #
  66. # Rack groups
  67. #
  68. class RackGroupSerializer(ValidatedModelSerializer):
  69. site = NestedSiteSerializer()
  70. class Meta:
  71. model = RackGroup
  72. fields = ['id', 'name', 'slug', 'site']
  73. class NestedRackGroupSerializer(WritableNestedSerializer):
  74. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
  75. class Meta:
  76. model = RackGroup
  77. fields = ['id', 'url', 'name', 'slug']
  78. #
  79. # Rack roles
  80. #
  81. class RackRoleSerializer(ValidatedModelSerializer):
  82. class Meta:
  83. model = RackRole
  84. fields = ['id', 'name', 'slug', 'color']
  85. class NestedRackRoleSerializer(WritableNestedSerializer):
  86. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
  87. class Meta:
  88. model = RackRole
  89. fields = ['id', 'url', 'name', 'slug']
  90. #
  91. # Racks
  92. #
  93. class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
  94. site = NestedSiteSerializer()
  95. group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
  96. tenant = NestedTenantSerializer(required=False, allow_null=True)
  97. role = NestedRackRoleSerializer(required=False, allow_null=True)
  98. type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
  99. width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
  100. tags = TagListSerializerField(required=False)
  101. class Meta:
  102. model = Rack
  103. fields = [
  104. 'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
  105. 'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
  106. ]
  107. # Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
  108. # prevents facility_id from being interpreted as a required field.
  109. validators = [
  110. UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'name'))
  111. ]
  112. def validate(self, data):
  113. # Validate uniqueness of (group, facility_id) since we omitted the automatically-created validator from Meta.
  114. if data.get('facility_id', None):
  115. validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'facility_id'))
  116. validator.set_context(self)
  117. validator(data)
  118. # Enforce model validation
  119. super(RackSerializer, self).validate(data)
  120. return data
  121. class NestedRackSerializer(WritableNestedSerializer):
  122. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
  123. class Meta:
  124. model = Rack
  125. fields = ['id', 'url', 'name', 'display_name']
  126. #
  127. # Rack units
  128. #
  129. class NestedDeviceSerializer(WritableNestedSerializer):
  130. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
  131. class Meta:
  132. model = Device
  133. fields = ['id', 'url', 'name', 'display_name']
  134. class RackUnitSerializer(serializers.Serializer):
  135. """
  136. A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.
  137. """
  138. id = serializers.IntegerField(read_only=True)
  139. name = serializers.CharField(read_only=True)
  140. face = serializers.IntegerField(read_only=True)
  141. device = NestedDeviceSerializer(read_only=True)
  142. #
  143. # Rack reservations
  144. #
  145. class RackReservationSerializer(ValidatedModelSerializer):
  146. rack = NestedRackSerializer()
  147. user = NestedUserSerializer()
  148. tenant = NestedTenantSerializer(required=False, allow_null=True)
  149. class Meta:
  150. model = RackReservation
  151. fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
  152. #
  153. # Manufacturers
  154. #
  155. class ManufacturerSerializer(ValidatedModelSerializer):
  156. class Meta:
  157. model = Manufacturer
  158. fields = ['id', 'name', 'slug']
  159. class NestedManufacturerSerializer(WritableNestedSerializer):
  160. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
  161. class Meta:
  162. model = Manufacturer
  163. fields = ['id', 'url', 'name', 'slug']
  164. #
  165. # Device types
  166. #
  167. class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
  168. manufacturer = NestedManufacturerSerializer()
  169. interface_ordering = ChoiceField(choices=IFACE_ORDERING_CHOICES, required=False)
  170. subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
  171. instance_count = serializers.IntegerField(source='instances.count', read_only=True)
  172. tags = TagListSerializerField(required=False)
  173. class Meta:
  174. model = DeviceType
  175. fields = [
  176. 'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
  177. 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'tags', 'custom_fields',
  178. 'created', 'last_updated', 'instance_count',
  179. ]
  180. class NestedDeviceTypeSerializer(WritableNestedSerializer):
  181. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
  182. manufacturer = NestedManufacturerSerializer(read_only=True)
  183. class Meta:
  184. model = DeviceType
  185. fields = ['id', 'url', 'manufacturer', 'model', 'slug']
  186. #
  187. # Console port templates
  188. #
  189. class ConsolePortTemplateSerializer(ValidatedModelSerializer):
  190. device_type = NestedDeviceTypeSerializer()
  191. class Meta:
  192. model = ConsolePortTemplate
  193. fields = ['id', 'device_type', 'name']
  194. #
  195. # Console server port templates
  196. #
  197. class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
  198. device_type = NestedDeviceTypeSerializer()
  199. class Meta:
  200. model = ConsoleServerPortTemplate
  201. fields = ['id', 'device_type', 'name']
  202. #
  203. # Power port templates
  204. #
  205. class PowerPortTemplateSerializer(ValidatedModelSerializer):
  206. device_type = NestedDeviceTypeSerializer()
  207. class Meta:
  208. model = PowerPortTemplate
  209. fields = ['id', 'device_type', 'name']
  210. #
  211. # Power outlet templates
  212. #
  213. class PowerOutletTemplateSerializer(ValidatedModelSerializer):
  214. device_type = NestedDeviceTypeSerializer()
  215. class Meta:
  216. model = PowerOutletTemplate
  217. fields = ['id', 'device_type', 'name']
  218. #
  219. # Interface templates
  220. #
  221. class InterfaceTemplateSerializer(ValidatedModelSerializer):
  222. device_type = NestedDeviceTypeSerializer()
  223. form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
  224. class Meta:
  225. model = InterfaceTemplate
  226. fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
  227. #
  228. # Device bay templates
  229. #
  230. class DeviceBayTemplateSerializer(ValidatedModelSerializer):
  231. device_type = NestedDeviceTypeSerializer()
  232. class Meta:
  233. model = DeviceBayTemplate
  234. fields = ['id', 'device_type', 'name']
  235. #
  236. # Device roles
  237. #
  238. class DeviceRoleSerializer(ValidatedModelSerializer):
  239. class Meta:
  240. model = DeviceRole
  241. fields = ['id', 'name', 'slug', 'color', 'vm_role']
  242. class NestedDeviceRoleSerializer(WritableNestedSerializer):
  243. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
  244. class Meta:
  245. model = DeviceRole
  246. fields = ['id', 'url', 'name', 'slug']
  247. #
  248. # Platforms
  249. #
  250. class PlatformSerializer(ValidatedModelSerializer):
  251. manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
  252. class Meta:
  253. model = Platform
  254. fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
  255. class NestedPlatformSerializer(WritableNestedSerializer):
  256. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
  257. class Meta:
  258. model = Platform
  259. fields = ['id', 'url', 'name', 'slug']
  260. #
  261. # Devices
  262. #
  263. # Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
  264. class DeviceIPAddressSerializer(WritableNestedSerializer):
  265. url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
  266. class Meta:
  267. model = IPAddress
  268. fields = ['id', 'url', 'family', 'address']
  269. # Cannot import virtualization.api.NestedClusterSerializer due to circular dependency
  270. class NestedClusterSerializer(WritableNestedSerializer):
  271. url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
  272. class Meta:
  273. model = Cluster
  274. fields = ['id', 'url', 'name']
  275. # Cannot import NestedVirtualChassisSerializer due to circular dependency
  276. class DeviceVirtualChassisSerializer(WritableNestedSerializer):
  277. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
  278. master = NestedDeviceSerializer()
  279. class Meta:
  280. model = VirtualChassis
  281. fields = ['id', 'url', 'master']
  282. class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
  283. device_type = NestedDeviceTypeSerializer()
  284. device_role = NestedDeviceRoleSerializer()
  285. tenant = NestedTenantSerializer(required=False, allow_null=True)
  286. platform = NestedPlatformSerializer(required=False, allow_null=True)
  287. site = NestedSiteSerializer()
  288. rack = NestedRackSerializer(required=False, allow_null=True)
  289. face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
  290. status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
  291. primary_ip = DeviceIPAddressSerializer(read_only=True)
  292. primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
  293. primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
  294. parent_device = serializers.SerializerMethodField()
  295. cluster = NestedClusterSerializer(required=False, allow_null=True)
  296. virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True)
  297. tags = TagListSerializerField(required=False)
  298. class Meta:
  299. model = Device
  300. fields = [
  301. 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
  302. 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
  303. 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created',
  304. 'last_updated', 'local_context_data',
  305. ]
  306. validators = []
  307. def validate(self, data):
  308. # Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
  309. if data.get('rack') and data.get('position') and data.get('face'):
  310. validator = UniqueTogetherValidator(queryset=Device.objects.all(), fields=('rack', 'position', 'face'))
  311. validator.set_context(self)
  312. validator(data)
  313. # Enforce model validation
  314. super(DeviceSerializer, self).validate(data)
  315. return data
  316. def get_parent_device(self, obj):
  317. try:
  318. device_bay = obj.parent_bay
  319. except DeviceBay.DoesNotExist:
  320. return None
  321. context = {'request': self.context['request']}
  322. data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
  323. data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
  324. return data
  325. class DeviceWithConfigContextSerializer(DeviceSerializer):
  326. config_context = serializers.SerializerMethodField()
  327. class Meta(DeviceSerializer.Meta):
  328. fields = [
  329. 'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
  330. 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
  331. 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
  332. 'config_context', 'created', 'last_updated', 'local_context_data',
  333. ]
  334. def get_config_context(self, obj):
  335. return obj.get_config_context()
  336. #
  337. # Console server ports
  338. #
  339. class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
  340. device = NestedDeviceSerializer()
  341. tags = TagListSerializerField(required=False)
  342. class Meta:
  343. model = ConsoleServerPort
  344. fields = ['id', 'device', 'name', 'connected_console', 'tags']
  345. read_only_fields = ['connected_console']
  346. class NestedConsoleServerPortSerializer(WritableNestedSerializer):
  347. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
  348. device = NestedDeviceSerializer(read_only=True)
  349. is_connected = serializers.SerializerMethodField(read_only=True)
  350. class Meta:
  351. model = ConsoleServerPort
  352. fields = ['id', 'url', 'device', 'name', 'is_connected']
  353. def get_is_connected(self, obj):
  354. return hasattr(obj, 'connected_console') and obj.connected_console is not None
  355. #
  356. # Console ports
  357. #
  358. class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
  359. device = NestedDeviceSerializer()
  360. cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True)
  361. tags = TagListSerializerField(required=False)
  362. class Meta:
  363. model = ConsolePort
  364. fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags']
  365. class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
  366. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
  367. device = NestedDeviceSerializer(read_only=True)
  368. is_connected = serializers.SerializerMethodField(read_only=True)
  369. class Meta:
  370. model = ConsolePort
  371. fields = ['id', 'url', 'device', 'name', 'is_connected']
  372. def get_is_connected(self, obj):
  373. return obj.cs_port is not None
  374. #
  375. # Power outlets
  376. #
  377. class PowerOutletSerializer(TaggitSerializer, ValidatedModelSerializer):
  378. device = NestedDeviceSerializer()
  379. tags = TagListSerializerField(required=False)
  380. class Meta:
  381. model = PowerOutlet
  382. fields = ['id', 'device', 'name', 'connected_port', 'tags']
  383. read_only_fields = ['connected_port']
  384. class NestedPowerOutletSerializer(WritableNestedSerializer):
  385. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
  386. device = NestedDeviceSerializer(read_only=True)
  387. is_connected = serializers.SerializerMethodField(read_only=True)
  388. class Meta:
  389. model = PowerOutlet
  390. fields = ['id', 'url', 'device', 'name', 'is_connected']
  391. def get_is_connected(self, obj):
  392. return hasattr(obj, 'connected_port') and obj.connected_port is not None
  393. #
  394. # Power ports
  395. #
  396. class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
  397. device = NestedDeviceSerializer()
  398. power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True)
  399. tags = TagListSerializerField(required=False)
  400. class Meta:
  401. model = PowerPort
  402. fields = ['id', 'device', 'name', 'power_outlet', 'connection_status', 'tags']
  403. class NestedPowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
  404. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
  405. device = NestedDeviceSerializer(read_only=True)
  406. is_connected = serializers.SerializerMethodField(read_only=True)
  407. class Meta:
  408. model = PowerPort
  409. fields = ['id', 'url', 'device', 'name', 'is_connected']
  410. def get_is_connected(self, obj):
  411. return obj.power_outlet is not None
  412. #
  413. # Interfaces
  414. #
  415. class IsConnectedMixin(object):
  416. """
  417. Provide a method for setting is_connected on Interface serializers.
  418. """
  419. def get_is_connected(self, obj):
  420. """
  421. Return True if the interface has a connected interface or circuit.
  422. """
  423. if obj.connection:
  424. return True
  425. if hasattr(obj, 'circuit_termination') and obj.circuit_termination is not None:
  426. return True
  427. return False
  428. class NestedInterfaceSerializer(IsConnectedMixin, WritableNestedSerializer):
  429. device = NestedDeviceSerializer(read_only=True)
  430. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
  431. is_connected = serializers.SerializerMethodField(read_only=True)
  432. class Meta:
  433. model = Interface
  434. fields = ['id', 'url', 'device', 'name', 'is_connected']
  435. class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
  436. url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
  437. class Meta:
  438. model = Circuit
  439. fields = ['id', 'url', 'cid']
  440. class InterfaceCircuitTerminationSerializer(WritableNestedSerializer):
  441. circuit = InterfaceNestedCircuitSerializer(read_only=True)
  442. class Meta:
  443. model = CircuitTermination
  444. fields = [
  445. 'id', 'circuit', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
  446. ]
  447. # Cannot import ipam.api.NestedVLANSerializer due to circular dependency
  448. class InterfaceVLANSerializer(WritableNestedSerializer):
  449. url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
  450. class Meta:
  451. model = VLAN
  452. fields = ['id', 'url', 'vid', 'name', 'display_name']
  453. class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSerializer):
  454. device = NestedDeviceSerializer()
  455. form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
  456. lag = NestedInterfaceSerializer(required=False, allow_null=True)
  457. is_connected = serializers.SerializerMethodField(read_only=True)
  458. interface_connection = serializers.SerializerMethodField(read_only=True)
  459. circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
  460. mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
  461. untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
  462. tagged_vlans = SerializedPKRelatedField(
  463. queryset=VLAN.objects.all(),
  464. serializer=InterfaceVLANSerializer,
  465. required=False,
  466. many=True
  467. )
  468. tags = TagListSerializerField(required=False)
  469. class Meta:
  470. model = Interface
  471. fields = [
  472. 'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
  473. 'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
  474. 'tags',
  475. ]
  476. def validate(self, data):
  477. # All associated VLANs be global or assigned to the parent device's site.
  478. device = self.instance.device if self.instance else data.get('device')
  479. untagged_vlan = data.get('untagged_vlan')
  480. if untagged_vlan and untagged_vlan.site not in [device.site, None]:
  481. raise serializers.ValidationError({
  482. 'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
  483. "global.".format(untagged_vlan)
  484. })
  485. for vlan in data.get('tagged_vlans', []):
  486. if vlan.site not in [device.site, None]:
  487. raise serializers.ValidationError({
  488. 'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
  489. "be global.".format(vlan)
  490. })
  491. return super(InterfaceSerializer, self).validate(data)
  492. def get_interface_connection(self, obj):
  493. if obj.connection:
  494. context = {
  495. 'request': self.context['request'],
  496. 'interface': obj.connected_interface,
  497. }
  498. return ContextualInterfaceConnectionSerializer(obj.connection, context=context).data
  499. return None
  500. #
  501. # Device bays
  502. #
  503. class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
  504. device = NestedDeviceSerializer()
  505. installed_device = NestedDeviceSerializer(required=False, allow_null=True)
  506. tags = TagListSerializerField(required=False)
  507. class Meta:
  508. model = DeviceBay
  509. fields = ['id', 'device', 'name', 'installed_device', 'tags']
  510. class NestedDeviceBaySerializer(WritableNestedSerializer):
  511. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
  512. device = NestedDeviceSerializer(read_only=True)
  513. class Meta:
  514. model = DeviceBay
  515. fields = ['id', 'url', 'device', 'name']
  516. #
  517. # Inventory items
  518. #
  519. class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
  520. device = NestedDeviceSerializer()
  521. # Provide a default value to satisfy UniqueTogetherValidator
  522. parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
  523. manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
  524. tags = TagListSerializerField(required=False)
  525. class Meta:
  526. model = InventoryItem
  527. fields = [
  528. 'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
  529. 'description', 'tags',
  530. ]
  531. #
  532. # Interface connections
  533. #
  534. class InterfaceConnectionSerializer(ValidatedModelSerializer):
  535. interface_a = NestedInterfaceSerializer()
  536. interface_b = NestedInterfaceSerializer()
  537. connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
  538. class Meta:
  539. model = InterfaceConnection
  540. fields = ['id', 'interface_a', 'interface_b', 'connection_status']
  541. class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
  542. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
  543. class Meta:
  544. model = InterfaceConnection
  545. fields = ['id', 'url', 'connection_status']
  546. class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer):
  547. """
  548. A read-only representation of an InterfaceConnection from the perspective of either of its two connected Interfaces.
  549. """
  550. interface = serializers.SerializerMethodField(read_only=True)
  551. connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
  552. class Meta:
  553. model = InterfaceConnection
  554. fields = ['id', 'interface', 'connection_status']
  555. def get_interface(self, obj):
  556. return NestedInterfaceSerializer(self.context['interface'], context=self.context).data
  557. #
  558. # Virtual chassis
  559. #
  560. class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
  561. master = NestedDeviceSerializer()
  562. tags = TagListSerializerField(required=False)
  563. class Meta:
  564. model = VirtualChassis
  565. fields = ['id', 'master', 'domain', 'tags']
  566. class NestedVirtualChassisSerializer(WritableNestedSerializer):
  567. url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
  568. class Meta:
  569. model = VirtualChassis
  570. fields = ['id', 'url']