serializers.py 16 KB

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