serializers.py 14 KB

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