serializers.py 18 KB

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