serializers.py 16 KB

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