serializers.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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,
  8. )
  9. from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
  10. from extras.choices import *
  11. from extras.models import (
  12. ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
  13. )
  14. from extras.utils import FeatureQuery
  15. from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
  16. from netbox.api.exceptions import SerializerNotFound
  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. #
  25. # Custom fields
  26. #
  27. class CustomFieldSerializer(ValidatedModelSerializer):
  28. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
  29. content_types = ContentTypeField(
  30. queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
  31. many=True
  32. )
  33. type = ChoiceField(choices=CustomFieldTypeChoices)
  34. filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
  35. class Meta:
  36. model = CustomField
  37. fields = [
  38. 'id', 'url', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
  39. 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices',
  40. ]
  41. #
  42. # Export templates
  43. #
  44. class ExportTemplateSerializer(ValidatedModelSerializer):
  45. url = serializers.HyperlinkedIdentityField(view_name='extras-api:exporttemplate-detail')
  46. content_type = ContentTypeField(
  47. queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
  48. )
  49. class Meta:
  50. model = ExportTemplate
  51. fields = ['id', 'url', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension']
  52. #
  53. # Tags
  54. #
  55. class TagSerializer(ValidatedModelSerializer):
  56. url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
  57. tagged_items = serializers.IntegerField(read_only=True)
  58. class Meta:
  59. model = Tag
  60. fields = ['id', 'url', 'name', 'slug', 'color', 'description', 'tagged_items']
  61. class TaggedObjectSerializer(serializers.Serializer):
  62. tags = NestedTagSerializer(many=True, required=False)
  63. def create(self, validated_data):
  64. tags = validated_data.pop('tags', None)
  65. instance = super().create(validated_data)
  66. if tags is not None:
  67. return self._save_tags(instance, tags)
  68. return instance
  69. def update(self, instance, validated_data):
  70. tags = validated_data.pop('tags', None)
  71. # Cache tags on instance for change logging
  72. instance._tags = tags or []
  73. instance = super().update(instance, validated_data)
  74. if tags is not None:
  75. return self._save_tags(instance, tags)
  76. return instance
  77. def _save_tags(self, instance, tags):
  78. if tags:
  79. instance.tags.set(*[t.name for t in tags])
  80. else:
  81. instance.tags.clear()
  82. return instance
  83. #
  84. # Image attachments
  85. #
  86. class ImageAttachmentSerializer(ValidatedModelSerializer):
  87. url = serializers.HyperlinkedIdentityField(view_name='extras-api:imageattachment-detail')
  88. content_type = ContentTypeField(
  89. queryset=ContentType.objects.all()
  90. )
  91. parent = serializers.SerializerMethodField(read_only=True)
  92. class Meta:
  93. model = ImageAttachment
  94. fields = [
  95. 'id', 'url', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height', 'image_width',
  96. 'created',
  97. ]
  98. def validate(self, data):
  99. # Validate that the parent object exists
  100. try:
  101. data['content_type'].get_object_for_this_type(id=data['object_id'])
  102. except ObjectDoesNotExist:
  103. raise serializers.ValidationError(
  104. "Invalid parent object: {} ID {}".format(data['content_type'], data['object_id'])
  105. )
  106. # Enforce model validation
  107. super().validate(data)
  108. return data
  109. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  110. def get_parent(self, obj):
  111. # Static mapping of models to their nested serializers
  112. if isinstance(obj.parent, Device):
  113. serializer = NestedDeviceSerializer
  114. elif isinstance(obj.parent, Rack):
  115. serializer = NestedRackSerializer
  116. elif isinstance(obj.parent, Site):
  117. serializer = NestedSiteSerializer
  118. else:
  119. raise Exception("Unexpected type of parent object for ImageAttachment")
  120. return serializer(obj.parent, context={'request': self.context['request']}).data
  121. #
  122. # Config contexts
  123. #
  124. class ConfigContextSerializer(ValidatedModelSerializer):
  125. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
  126. regions = SerializedPKRelatedField(
  127. queryset=Region.objects.all(),
  128. serializer=NestedRegionSerializer,
  129. required=False,
  130. many=True
  131. )
  132. sites = SerializedPKRelatedField(
  133. queryset=Site.objects.all(),
  134. serializer=NestedSiteSerializer,
  135. required=False,
  136. many=True
  137. )
  138. roles = SerializedPKRelatedField(
  139. queryset=DeviceRole.objects.all(),
  140. serializer=NestedDeviceRoleSerializer,
  141. required=False,
  142. many=True
  143. )
  144. platforms = SerializedPKRelatedField(
  145. queryset=Platform.objects.all(),
  146. serializer=NestedPlatformSerializer,
  147. required=False,
  148. many=True
  149. )
  150. cluster_groups = SerializedPKRelatedField(
  151. queryset=ClusterGroup.objects.all(),
  152. serializer=NestedClusterGroupSerializer,
  153. required=False,
  154. many=True
  155. )
  156. clusters = SerializedPKRelatedField(
  157. queryset=Cluster.objects.all(),
  158. serializer=NestedClusterSerializer,
  159. required=False,
  160. many=True
  161. )
  162. tenant_groups = SerializedPKRelatedField(
  163. queryset=TenantGroup.objects.all(),
  164. serializer=NestedTenantGroupSerializer,
  165. required=False,
  166. many=True
  167. )
  168. tenants = SerializedPKRelatedField(
  169. queryset=Tenant.objects.all(),
  170. serializer=NestedTenantSerializer,
  171. required=False,
  172. many=True
  173. )
  174. tags = serializers.SlugRelatedField(
  175. queryset=Tag.objects.all(),
  176. slug_field='slug',
  177. required=False,
  178. many=True
  179. )
  180. class Meta:
  181. model = ConfigContext
  182. fields = [
  183. 'id', 'url', 'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms',
  184. 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data', 'created', 'last_updated',
  185. ]
  186. #
  187. # Job Results
  188. #
  189. class JobResultSerializer(serializers.ModelSerializer):
  190. url = serializers.HyperlinkedIdentityField(view_name='extras-api:jobresult-detail')
  191. user = NestedUserSerializer(
  192. read_only=True
  193. )
  194. status = ChoiceField(choices=JobResultStatusChoices, read_only=True)
  195. obj_type = ContentTypeField(
  196. read_only=True
  197. )
  198. class Meta:
  199. model = JobResult
  200. fields = [
  201. 'id', 'url', 'created', 'completed', 'name', 'obj_type', 'status', 'user', 'data', 'job_id',
  202. ]
  203. #
  204. # Reports
  205. #
  206. class ReportSerializer(serializers.Serializer):
  207. url = serializers.HyperlinkedIdentityField(
  208. view_name='extras-api:report-detail',
  209. lookup_field='full_name',
  210. lookup_url_kwarg='pk'
  211. )
  212. id = serializers.CharField(read_only=True, source="full_name")
  213. module = serializers.CharField(max_length=255)
  214. name = serializers.CharField(max_length=255)
  215. description = serializers.CharField(max_length=255, required=False)
  216. test_methods = serializers.ListField(child=serializers.CharField(max_length=255))
  217. result = NestedJobResultSerializer()
  218. class ReportDetailSerializer(ReportSerializer):
  219. result = JobResultSerializer()
  220. #
  221. # Scripts
  222. #
  223. class ScriptSerializer(serializers.Serializer):
  224. url = serializers.HyperlinkedIdentityField(
  225. view_name='extras-api:script-detail',
  226. lookup_field='full_name',
  227. lookup_url_kwarg='pk'
  228. )
  229. id = serializers.CharField(read_only=True, source="full_name")
  230. module = serializers.CharField(max_length=255)
  231. name = serializers.CharField(read_only=True)
  232. description = serializers.CharField(read_only=True)
  233. vars = serializers.SerializerMethodField(read_only=True)
  234. result = NestedJobResultSerializer()
  235. def get_vars(self, instance):
  236. return {
  237. k: v.__class__.__name__ for k, v in instance._get_vars().items()
  238. }
  239. class ScriptDetailSerializer(ScriptSerializer):
  240. result = JobResultSerializer()
  241. class ScriptInputSerializer(serializers.Serializer):
  242. data = serializers.JSONField()
  243. commit = serializers.BooleanField()
  244. class ScriptLogMessageSerializer(serializers.Serializer):
  245. status = serializers.SerializerMethodField(read_only=True)
  246. message = serializers.SerializerMethodField(read_only=True)
  247. def get_status(self, instance):
  248. return instance[0]
  249. def get_message(self, instance):
  250. return instance[1]
  251. class ScriptOutputSerializer(serializers.Serializer):
  252. log = ScriptLogMessageSerializer(many=True, read_only=True)
  253. output = serializers.CharField(read_only=True)
  254. #
  255. # Change logging
  256. #
  257. class ObjectChangeSerializer(serializers.ModelSerializer):
  258. url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
  259. user = NestedUserSerializer(
  260. read_only=True
  261. )
  262. action = ChoiceField(
  263. choices=ObjectChangeActionChoices,
  264. read_only=True
  265. )
  266. changed_object_type = ContentTypeField(
  267. read_only=True
  268. )
  269. changed_object = serializers.SerializerMethodField(
  270. read_only=True
  271. )
  272. class Meta:
  273. model = ObjectChange
  274. fields = [
  275. 'id', 'url', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
  276. 'changed_object_id', 'changed_object', 'object_data',
  277. ]
  278. @swagger_serializer_method(serializer_or_field=serializers.DictField)
  279. def get_changed_object(self, obj):
  280. """
  281. Serialize a nested representation of the changed object.
  282. """
  283. if obj.changed_object is None:
  284. return None
  285. try:
  286. serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
  287. except SerializerNotFound:
  288. return obj.object_repr
  289. context = {
  290. 'request': self.context['request']
  291. }
  292. data = serializer(obj.changed_object, context=context).data
  293. return data
  294. #
  295. # ContentTypes
  296. #
  297. class ContentTypeSerializer(serializers.ModelSerializer):
  298. url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
  299. display_name = serializers.SerializerMethodField()
  300. class Meta:
  301. model = ContentType
  302. fields = ['id', 'url', 'app_label', 'model', 'display_name']
  303. @swagger_serializer_method(serializer_or_field=serializers.CharField)
  304. def get_display_name(self, obj):
  305. return obj.app_labeled_name