serializers.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. from django.contrib.auth.models import User
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.core.exceptions import ObjectDoesNotExist
  4. from drf_yasg.utils import swagger_serializer_method
  5. from rest_framework import serializers
  6. from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer
  7. from dcim.api.nested_serializers import (
  8. NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
  9. NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
  10. )
  11. from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
  12. from extras.choices import *
  13. from extras.models import *
  14. from extras.utils import FeatureQuery
  15. from netbox.api.exceptions import SerializerNotFound
  16. from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
  17. from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer, ValidatedModelSerializer
  18. from netbox.api.serializers.features import TaggableModelSerializer
  19. from netbox.constants import NESTED_SERIALIZER_PREFIX
  20. from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
  21. from tenancy.models import Tenant, TenantGroup
  22. from users.api.nested_serializers import NestedUserSerializer
  23. from utilities.api import get_serializer_for_model
  24. from virtualization.api.nested_serializers import (
  25. NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer,
  26. )
  27. from virtualization.models import Cluster, ClusterGroup, ClusterType
  28. from .nested_serializers import *
  29. __all__ = (
  30. 'ConfigContextSerializer',
  31. 'ConfigTemplateSerializer',
  32. 'ContentTypeSerializer',
  33. 'CustomFieldSerializer',
  34. 'CustomLinkSerializer',
  35. 'DashboardSerializer',
  36. 'ExportTemplateSerializer',
  37. 'ImageAttachmentSerializer',
  38. 'JobResultSerializer',
  39. 'JournalEntrySerializer',
  40. 'ObjectChangeSerializer',
  41. 'ReportDetailSerializer',
  42. 'ReportSerializer',
  43. 'ReportInputSerializer',
  44. 'SavedFilterSerializer',
  45. 'ScriptDetailSerializer',
  46. 'ScriptInputSerializer',
  47. 'ScriptLogMessageSerializer',
  48. 'ScriptOutputSerializer',
  49. 'ScriptSerializer',
  50. 'TagSerializer',
  51. 'WebhookSerializer',
  52. )
  53. #
  54. # Webhooks
  55. #
  56. class WebhookSerializer(ValidatedModelSerializer):
  57. url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail')
  58. content_types = ContentTypeField(
  59. queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
  60. many=True
  61. )
  62. class Meta:
  63. model = Webhook
  64. fields = [
  65. 'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete',
  66. 'type_job_start', 'type_job_end', 'payload_url', 'enabled', 'http_method', 'http_content_type',
  67. 'additional_headers', 'body_template', 'secret', 'conditions', 'ssl_verification', 'ca_file_path',
  68. 'created', 'last_updated',
  69. ]
  70. #
  71. # Custom fields
  72. #
  73. class CustomFieldSerializer(ValidatedModelSerializer):
  74. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
  75. content_types = ContentTypeField(
  76. queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
  77. many=True
  78. )
  79. type = ChoiceField(choices=CustomFieldTypeChoices)
  80. object_type = ContentTypeField(
  81. queryset=ContentType.objects.all(),
  82. required=False
  83. )
  84. filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
  85. data_type = serializers.SerializerMethodField()
  86. ui_visibility = ChoiceField(choices=CustomFieldVisibilityChoices, required=False)
  87. class Meta:
  88. model = CustomField
  89. fields = [
  90. 'id', 'url', 'display', 'content_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
  91. 'description', 'required', 'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'default',
  92. 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'created',
  93. 'last_updated',
  94. ]
  95. def get_data_type(self, obj):
  96. types = CustomFieldTypeChoices
  97. if obj.type == types.TYPE_INTEGER:
  98. return 'integer'
  99. if obj.type == types.TYPE_DECIMAL:
  100. return 'decimal'
  101. if obj.type == types.TYPE_BOOLEAN:
  102. return 'boolean'
  103. if obj.type in (types.TYPE_JSON, types.TYPE_OBJECT):
  104. return 'object'
  105. if obj.type in (types.TYPE_MULTISELECT, types.TYPE_MULTIOBJECT):
  106. return 'array'
  107. return 'string'
  108. #
  109. # Custom links
  110. #
  111. class CustomLinkSerializer(ValidatedModelSerializer):
  112. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail')
  113. content_types = ContentTypeField(
  114. queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query()),
  115. many=True
  116. )
  117. class Meta:
  118. model = CustomLink
  119. fields = [
  120. 'id', 'url', 'display', 'content_types', 'name', 'enabled', 'link_text', 'link_url', 'weight', 'group_name',
  121. 'button_class', 'new_window', 'created', 'last_updated',
  122. ]
  123. #
  124. # Export templates
  125. #
  126. class ExportTemplateSerializer(ValidatedModelSerializer):
  127. url = serializers.HyperlinkedIdentityField(view_name='extras-api:exporttemplate-detail')
  128. content_types = ContentTypeField(
  129. queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
  130. many=True
  131. )
  132. data_source = NestedDataSourceSerializer(
  133. required=False
  134. )
  135. data_file = NestedDataFileSerializer(
  136. read_only=True
  137. )
  138. class Meta:
  139. model = ExportTemplate
  140. fields = [
  141. 'id', 'url', 'display', 'content_types', 'name', 'description', 'template_code', 'mime_type',
  142. 'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file', 'data_synced', 'created',
  143. 'last_updated',
  144. ]
  145. #
  146. # Saved filters
  147. #
  148. class SavedFilterSerializer(ValidatedModelSerializer):
  149. url = serializers.HyperlinkedIdentityField(view_name='extras-api:savedfilter-detail')
  150. content_types = ContentTypeField(
  151. queryset=ContentType.objects.all(),
  152. many=True
  153. )
  154. class Meta:
  155. model = SavedFilter
  156. fields = [
  157. 'id', 'url', 'display', 'content_types', 'name', 'slug', 'description', 'user', 'weight', 'enabled',
  158. 'shared', 'parameters', 'created', 'last_updated',
  159. ]
  160. #
  161. # Tags
  162. #
  163. class TagSerializer(ValidatedModelSerializer):
  164. url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
  165. tagged_items = serializers.IntegerField(read_only=True)
  166. class Meta:
  167. model = Tag
  168. fields = [
  169. 'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tagged_items', 'created', 'last_updated',
  170. ]
  171. #
  172. # Image attachments
  173. #
  174. class ImageAttachmentSerializer(ValidatedModelSerializer):
  175. url = serializers.HyperlinkedIdentityField(view_name='extras-api:imageattachment-detail')
  176. content_type = ContentTypeField(
  177. queryset=ContentType.objects.all()
  178. )
  179. parent = serializers.SerializerMethodField(read_only=True)
  180. class Meta:
  181. model = ImageAttachment
  182. fields = [
  183. 'id', 'url', 'display', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height',
  184. 'image_width', 'created', 'last_updated',
  185. ]
  186. def validate(self, data):
  187. # Validate that the parent object exists
  188. try:
  189. data['content_type'].get_object_for_this_type(id=data['object_id'])
  190. except ObjectDoesNotExist:
  191. raise serializers.ValidationError(
  192. "Invalid parent object: {} ID {}".format(data['content_type'], data['object_id'])
  193. )
  194. # Enforce model validation
  195. super().validate(data)
  196. return data
  197. @swagger_serializer_method(serializer_or_field=serializers.JSONField)
  198. def get_parent(self, obj):
  199. serializer = get_serializer_for_model(obj.parent, prefix=NESTED_SERIALIZER_PREFIX)
  200. return serializer(obj.parent, context={'request': self.context['request']}).data
  201. #
  202. # Journal entries
  203. #
  204. class JournalEntrySerializer(NetBoxModelSerializer):
  205. url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
  206. assigned_object_type = ContentTypeField(
  207. queryset=ContentType.objects.all()
  208. )
  209. assigned_object = serializers.SerializerMethodField(read_only=True)
  210. created_by = serializers.PrimaryKeyRelatedField(
  211. allow_null=True,
  212. queryset=User.objects.all(),
  213. required=False,
  214. default=serializers.CurrentUserDefault()
  215. )
  216. kind = ChoiceField(
  217. choices=JournalEntryKindChoices,
  218. required=False
  219. )
  220. class Meta:
  221. model = JournalEntry
  222. fields = [
  223. 'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
  224. 'created_by', 'kind', 'comments', 'tags', 'custom_fields', 'last_updated',
  225. ]
  226. def validate(self, data):
  227. # Validate that the parent object exists
  228. if 'assigned_object_type' in data and 'assigned_object_id' in data:
  229. try:
  230. data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
  231. except ObjectDoesNotExist:
  232. raise serializers.ValidationError(
  233. f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
  234. )
  235. # Enforce model validation
  236. super().validate(data)
  237. return data
  238. @swagger_serializer_method(serializer_or_field=serializers.JSONField)
  239. def get_assigned_object(self, instance):
  240. serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix=NESTED_SERIALIZER_PREFIX)
  241. context = {'request': self.context['request']}
  242. return serializer(instance.assigned_object, context=context).data
  243. #
  244. # Config contexts
  245. #
  246. class ConfigContextSerializer(ValidatedModelSerializer):
  247. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
  248. regions = SerializedPKRelatedField(
  249. queryset=Region.objects.all(),
  250. serializer=NestedRegionSerializer,
  251. required=False,
  252. many=True
  253. )
  254. site_groups = SerializedPKRelatedField(
  255. queryset=SiteGroup.objects.all(),
  256. serializer=NestedSiteGroupSerializer,
  257. required=False,
  258. many=True
  259. )
  260. sites = SerializedPKRelatedField(
  261. queryset=Site.objects.all(),
  262. serializer=NestedSiteSerializer,
  263. required=False,
  264. many=True
  265. )
  266. locations = SerializedPKRelatedField(
  267. queryset=Location.objects.all(),
  268. serializer=NestedLocationSerializer,
  269. required=False,
  270. many=True
  271. )
  272. device_types = SerializedPKRelatedField(
  273. queryset=DeviceType.objects.all(),
  274. serializer=NestedDeviceTypeSerializer,
  275. required=False,
  276. many=True
  277. )
  278. roles = SerializedPKRelatedField(
  279. queryset=DeviceRole.objects.all(),
  280. serializer=NestedDeviceRoleSerializer,
  281. required=False,
  282. many=True
  283. )
  284. platforms = SerializedPKRelatedField(
  285. queryset=Platform.objects.all(),
  286. serializer=NestedPlatformSerializer,
  287. required=False,
  288. many=True
  289. )
  290. cluster_types = SerializedPKRelatedField(
  291. queryset=ClusterType.objects.all(),
  292. serializer=NestedClusterTypeSerializer,
  293. required=False,
  294. many=True
  295. )
  296. cluster_groups = SerializedPKRelatedField(
  297. queryset=ClusterGroup.objects.all(),
  298. serializer=NestedClusterGroupSerializer,
  299. required=False,
  300. many=True
  301. )
  302. clusters = SerializedPKRelatedField(
  303. queryset=Cluster.objects.all(),
  304. serializer=NestedClusterSerializer,
  305. required=False,
  306. many=True
  307. )
  308. tenant_groups = SerializedPKRelatedField(
  309. queryset=TenantGroup.objects.all(),
  310. serializer=NestedTenantGroupSerializer,
  311. required=False,
  312. many=True
  313. )
  314. tenants = SerializedPKRelatedField(
  315. queryset=Tenant.objects.all(),
  316. serializer=NestedTenantSerializer,
  317. required=False,
  318. many=True
  319. )
  320. tags = serializers.SlugRelatedField(
  321. queryset=Tag.objects.all(),
  322. slug_field='slug',
  323. required=False,
  324. many=True
  325. )
  326. data_source = NestedDataSourceSerializer(
  327. required=False
  328. )
  329. data_file = NestedDataFileSerializer(
  330. read_only=True
  331. )
  332. class Meta:
  333. model = ConfigContext
  334. fields = [
  335. 'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
  336. 'locations', 'device_types', 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters',
  337. 'tenant_groups', 'tenants', 'tags', 'data_source', 'data_path', 'data_file', 'data_synced', 'data',
  338. 'created', 'last_updated',
  339. ]
  340. #
  341. # Config templates
  342. #
  343. class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer):
  344. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail')
  345. data_source = NestedDataSourceSerializer(
  346. required=False
  347. )
  348. data_file = NestedDataFileSerializer(
  349. read_only=True
  350. )
  351. class Meta:
  352. model = ConfigTemplate
  353. fields = [
  354. 'id', 'url', 'display', 'name', 'description', 'environment_params', 'template_code', 'data_source',
  355. 'data_path', 'data_file', 'data_synced', 'tags', 'created', 'last_updated',
  356. ]
  357. #
  358. # Job Results
  359. #
  360. class JobResultSerializer(BaseModelSerializer):
  361. url = serializers.HyperlinkedIdentityField(view_name='extras-api:jobresult-detail')
  362. user = NestedUserSerializer(
  363. read_only=True
  364. )
  365. status = ChoiceField(choices=JobResultStatusChoices, read_only=True)
  366. obj_type = ContentTypeField(
  367. read_only=True
  368. )
  369. class Meta:
  370. model = JobResult
  371. fields = [
  372. 'id', 'url', 'display', 'status', 'created', 'scheduled', 'interval', 'started', 'completed', 'name',
  373. 'obj_type', 'user', 'data', 'job_id',
  374. ]
  375. #
  376. # Reports
  377. #
  378. class ReportSerializer(serializers.Serializer):
  379. url = serializers.HyperlinkedIdentityField(
  380. view_name='extras-api:report-detail',
  381. lookup_field='full_name',
  382. lookup_url_kwarg='pk'
  383. )
  384. id = serializers.CharField(read_only=True, source="full_name")
  385. module = serializers.CharField(max_length=255)
  386. name = serializers.CharField(max_length=255)
  387. description = serializers.CharField(max_length=255, required=False)
  388. test_methods = serializers.ListField(child=serializers.CharField(max_length=255))
  389. result = NestedJobResultSerializer()
  390. class ReportDetailSerializer(ReportSerializer):
  391. result = JobResultSerializer()
  392. class ReportInputSerializer(serializers.Serializer):
  393. schedule_at = serializers.DateTimeField(required=False, allow_null=True)
  394. interval = serializers.IntegerField(required=False, allow_null=True)
  395. #
  396. # Scripts
  397. #
  398. class ScriptSerializer(serializers.Serializer):
  399. url = serializers.HyperlinkedIdentityField(
  400. view_name='extras-api:script-detail',
  401. lookup_field='full_name',
  402. lookup_url_kwarg='pk'
  403. )
  404. id = serializers.CharField(read_only=True, source="full_name")
  405. module = serializers.CharField(max_length=255)
  406. name = serializers.CharField(read_only=True)
  407. description = serializers.CharField(read_only=True)
  408. vars = serializers.SerializerMethodField(read_only=True)
  409. result = NestedJobResultSerializer()
  410. @swagger_serializer_method(serializer_or_field=serializers.JSONField)
  411. def get_vars(self, instance):
  412. return {
  413. k: v.__class__.__name__ for k, v in instance._get_vars().items()
  414. }
  415. class ScriptDetailSerializer(ScriptSerializer):
  416. result = JobResultSerializer()
  417. class ScriptInputSerializer(serializers.Serializer):
  418. data = serializers.JSONField()
  419. commit = serializers.BooleanField()
  420. schedule_at = serializers.DateTimeField(required=False, allow_null=True)
  421. interval = serializers.IntegerField(required=False, allow_null=True)
  422. class ScriptLogMessageSerializer(serializers.Serializer):
  423. status = serializers.SerializerMethodField(read_only=True)
  424. message = serializers.SerializerMethodField(read_only=True)
  425. def get_status(self, instance):
  426. return instance[0]
  427. def get_message(self, instance):
  428. return instance[1]
  429. class ScriptOutputSerializer(serializers.Serializer):
  430. log = ScriptLogMessageSerializer(many=True, read_only=True)
  431. output = serializers.CharField(read_only=True)
  432. #
  433. # Change logging
  434. #
  435. class ObjectChangeSerializer(BaseModelSerializer):
  436. url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
  437. user = NestedUserSerializer(
  438. read_only=True
  439. )
  440. action = ChoiceField(
  441. choices=ObjectChangeActionChoices,
  442. read_only=True
  443. )
  444. changed_object_type = ContentTypeField(
  445. read_only=True
  446. )
  447. changed_object = serializers.SerializerMethodField(
  448. read_only=True
  449. )
  450. class Meta:
  451. model = ObjectChange
  452. fields = [
  453. 'id', 'url', 'display', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
  454. 'changed_object_id', 'changed_object', 'prechange_data', 'postchange_data',
  455. ]
  456. @swagger_serializer_method(serializer_or_field=serializers.JSONField)
  457. def get_changed_object(self, obj):
  458. """
  459. Serialize a nested representation of the changed object.
  460. """
  461. if obj.changed_object is None:
  462. return None
  463. try:
  464. serializer = get_serializer_for_model(obj.changed_object, prefix=NESTED_SERIALIZER_PREFIX)
  465. except SerializerNotFound:
  466. return obj.object_repr
  467. context = {
  468. 'request': self.context['request']
  469. }
  470. data = serializer(obj.changed_object, context=context).data
  471. return data
  472. #
  473. # ContentTypes
  474. #
  475. class ContentTypeSerializer(BaseModelSerializer):
  476. url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
  477. class Meta:
  478. model = ContentType
  479. fields = ['id', 'url', 'display', 'app_label', 'model']
  480. #
  481. # User dashboard
  482. #
  483. class DashboardSerializer(serializers.ModelSerializer):
  484. class Meta:
  485. model = Dashboard
  486. fields = ('layout', 'config')