serializers.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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, NestedPlatformSerializer, NestedRackSerializer,
  7. NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
  8. )
  9. from dcim.models import Device, DeviceRole, 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',
  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. class Meta:
  160. model = JournalEntry
  161. fields = [
  162. 'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
  163. 'created_by', 'comments',
  164. ]
  165. def validate(self, data):
  166. # Validate that the parent object exists
  167. if 'assigned_object_type' in data and 'assigned_object_id' in data:
  168. try:
  169. data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
  170. except ObjectDoesNotExist:
  171. raise serializers.ValidationError(
  172. f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
  173. )
  174. # Enforce model validation
  175. super().validate(data)
  176. return data
  177. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  178. def get_assigned_object(self, instance):
  179. serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix='Nested')
  180. context = {'request': self.context['request']}
  181. return serializer(instance.assigned_object, context=context).data
  182. #
  183. # Config contexts
  184. #
  185. class ConfigContextSerializer(ValidatedModelSerializer):
  186. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
  187. regions = SerializedPKRelatedField(
  188. queryset=Region.objects.all(),
  189. serializer=NestedRegionSerializer,
  190. required=False,
  191. many=True
  192. )
  193. site_groups = SerializedPKRelatedField(
  194. queryset=SiteGroup.objects.all(),
  195. serializer=NestedSiteGroupSerializer,
  196. required=False,
  197. many=True
  198. )
  199. sites = SerializedPKRelatedField(
  200. queryset=Site.objects.all(),
  201. serializer=NestedSiteSerializer,
  202. required=False,
  203. many=True
  204. )
  205. roles = SerializedPKRelatedField(
  206. queryset=DeviceRole.objects.all(),
  207. serializer=NestedDeviceRoleSerializer,
  208. required=False,
  209. many=True
  210. )
  211. platforms = SerializedPKRelatedField(
  212. queryset=Platform.objects.all(),
  213. serializer=NestedPlatformSerializer,
  214. required=False,
  215. many=True
  216. )
  217. cluster_groups = SerializedPKRelatedField(
  218. queryset=ClusterGroup.objects.all(),
  219. serializer=NestedClusterGroupSerializer,
  220. required=False,
  221. many=True
  222. )
  223. clusters = SerializedPKRelatedField(
  224. queryset=Cluster.objects.all(),
  225. serializer=NestedClusterSerializer,
  226. required=False,
  227. many=True
  228. )
  229. tenant_groups = SerializedPKRelatedField(
  230. queryset=TenantGroup.objects.all(),
  231. serializer=NestedTenantGroupSerializer,
  232. required=False,
  233. many=True
  234. )
  235. tenants = SerializedPKRelatedField(
  236. queryset=Tenant.objects.all(),
  237. serializer=NestedTenantSerializer,
  238. required=False,
  239. many=True
  240. )
  241. tags = serializers.SlugRelatedField(
  242. queryset=Tag.objects.all(),
  243. slug_field='slug',
  244. required=False,
  245. many=True
  246. )
  247. class Meta:
  248. model = ConfigContext
  249. fields = [
  250. 'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
  251. 'roles', 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data', 'created',
  252. 'last_updated',
  253. ]
  254. #
  255. # Job Results
  256. #
  257. class JobResultSerializer(BaseModelSerializer):
  258. url = serializers.HyperlinkedIdentityField(view_name='extras-api:jobresult-detail')
  259. user = NestedUserSerializer(
  260. read_only=True
  261. )
  262. status = ChoiceField(choices=JobResultStatusChoices, read_only=True)
  263. obj_type = ContentTypeField(
  264. read_only=True
  265. )
  266. class Meta:
  267. model = JobResult
  268. fields = [
  269. 'id', 'url', 'display', 'created', 'completed', 'name', 'obj_type', 'status', 'user', 'data', 'job_id',
  270. ]
  271. #
  272. # Reports
  273. #
  274. class ReportSerializer(serializers.Serializer):
  275. url = serializers.HyperlinkedIdentityField(
  276. view_name='extras-api:report-detail',
  277. lookup_field='full_name',
  278. lookup_url_kwarg='pk'
  279. )
  280. id = serializers.CharField(read_only=True, source="full_name")
  281. module = serializers.CharField(max_length=255)
  282. name = serializers.CharField(max_length=255)
  283. description = serializers.CharField(max_length=255, required=False)
  284. test_methods = serializers.ListField(child=serializers.CharField(max_length=255))
  285. result = NestedJobResultSerializer()
  286. class ReportDetailSerializer(ReportSerializer):
  287. result = JobResultSerializer()
  288. #
  289. # Scripts
  290. #
  291. class ScriptSerializer(serializers.Serializer):
  292. url = serializers.HyperlinkedIdentityField(
  293. view_name='extras-api:script-detail',
  294. lookup_field='full_name',
  295. lookup_url_kwarg='pk'
  296. )
  297. id = serializers.CharField(read_only=True, source="full_name")
  298. module = serializers.CharField(max_length=255)
  299. name = serializers.CharField(read_only=True)
  300. description = serializers.CharField(read_only=True)
  301. vars = serializers.SerializerMethodField(read_only=True)
  302. result = NestedJobResultSerializer()
  303. def get_vars(self, instance):
  304. return {
  305. k: v.__class__.__name__ for k, v in instance._get_vars().items()
  306. }
  307. class ScriptDetailSerializer(ScriptSerializer):
  308. result = JobResultSerializer()
  309. class ScriptInputSerializer(serializers.Serializer):
  310. data = serializers.JSONField()
  311. commit = serializers.BooleanField()
  312. class ScriptLogMessageSerializer(serializers.Serializer):
  313. status = serializers.SerializerMethodField(read_only=True)
  314. message = serializers.SerializerMethodField(read_only=True)
  315. def get_status(self, instance):
  316. return instance[0]
  317. def get_message(self, instance):
  318. return instance[1]
  319. class ScriptOutputSerializer(serializers.Serializer):
  320. log = ScriptLogMessageSerializer(many=True, read_only=True)
  321. output = serializers.CharField(read_only=True)
  322. #
  323. # Change logging
  324. #
  325. class ObjectChangeSerializer(BaseModelSerializer):
  326. url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
  327. user = NestedUserSerializer(
  328. read_only=True
  329. )
  330. action = ChoiceField(
  331. choices=ObjectChangeActionChoices,
  332. read_only=True
  333. )
  334. changed_object_type = ContentTypeField(
  335. read_only=True
  336. )
  337. changed_object = serializers.SerializerMethodField(
  338. read_only=True
  339. )
  340. class Meta:
  341. model = ObjectChange
  342. fields = [
  343. 'id', 'url', 'display', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
  344. 'changed_object_id', 'changed_object', 'prechange_data', 'postchange_data',
  345. ]
  346. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  347. def get_changed_object(self, obj):
  348. """
  349. Serialize a nested representation of the changed object.
  350. """
  351. if obj.changed_object is None:
  352. return None
  353. try:
  354. serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
  355. except SerializerNotFound:
  356. return obj.object_repr
  357. context = {
  358. 'request': self.context['request']
  359. }
  360. data = serializer(obj.changed_object, context=context).data
  361. return data
  362. #
  363. # ContentTypes
  364. #
  365. class ContentTypeSerializer(BaseModelSerializer):
  366. url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
  367. display_name = serializers.SerializerMethodField()
  368. class Meta:
  369. model = ContentType
  370. fields = ['id', 'url', 'display', 'app_label', 'model', 'display_name']
  371. @swagger_serializer_method(serializer_or_field=serializers.CharField)
  372. def get_display_name(self, obj):
  373. return obj.app_labeled_name