serializers.py 14 KB

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