serializers.py 17 KB

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