serializers.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. from django.contrib.auth import get_user_model
  2. from django.core.exceptions import ObjectDoesNotExist
  3. from django.utils.translation import gettext as _
  4. from drf_spectacular.types import OpenApiTypes
  5. from drf_spectacular.utils import extend_schema_field
  6. from rest_framework import serializers
  7. from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer
  8. from core.api.serializers import JobSerializer
  9. from core.models import ObjectType
  10. from dcim.api.nested_serializers import (
  11. NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
  12. NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
  13. )
  14. from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
  15. from extras.choices import *
  16. from extras.models import *
  17. from netbox.api.exceptions import SerializerNotFound
  18. from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField, SerializedPKRelatedField
  19. from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer, ValidatedModelSerializer
  20. from netbox.api.serializers.features import TaggableModelSerializer
  21. from netbox.constants import NESTED_SERIALIZER_PREFIX
  22. from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
  23. from tenancy.models import Tenant, TenantGroup
  24. from users.api.nested_serializers import NestedUserSerializer
  25. from utilities.api import get_serializer_for_model
  26. from virtualization.api.nested_serializers import (
  27. NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer,
  28. )
  29. from virtualization.models import Cluster, ClusterGroup, ClusterType
  30. from .nested_serializers import *
  31. __all__ = (
  32. 'BookmarkSerializer',
  33. 'ConfigContextSerializer',
  34. 'ConfigTemplateSerializer',
  35. 'ContentTypeSerializer',
  36. 'CustomFieldChoiceSetSerializer',
  37. 'CustomFieldSerializer',
  38. 'CustomLinkSerializer',
  39. 'DashboardSerializer',
  40. 'EventRuleSerializer',
  41. 'ExportTemplateSerializer',
  42. 'ImageAttachmentSerializer',
  43. 'JournalEntrySerializer',
  44. 'ObjectChangeSerializer',
  45. 'SavedFilterSerializer',
  46. 'ScriptDetailSerializer',
  47. 'ScriptInputSerializer',
  48. 'ScriptSerializer',
  49. 'TagSerializer',
  50. 'WebhookSerializer',
  51. )
  52. #
  53. # Event Rules
  54. #
  55. class EventRuleSerializer(NetBoxModelSerializer):
  56. url = serializers.HyperlinkedIdentityField(view_name='extras-api:eventrule-detail')
  57. content_types = ContentTypeField(
  58. queryset=ObjectType.objects.with_feature('event_rules'),
  59. many=True
  60. )
  61. action_type = ChoiceField(choices=EventRuleActionChoices)
  62. action_object_type = ContentTypeField(
  63. queryset=ObjectType.objects.with_feature('event_rules'),
  64. )
  65. action_object = serializers.SerializerMethodField(read_only=True)
  66. class Meta:
  67. model = EventRule
  68. fields = [
  69. 'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete',
  70. 'type_job_start', 'type_job_end', 'enabled', 'conditions', 'action_type', 'action_object_type',
  71. 'action_object_id', 'action_object', 'description', 'custom_fields', 'tags', 'created', 'last_updated',
  72. ]
  73. brief_fields = ('id', 'url', 'display', 'name', 'description')
  74. @extend_schema_field(OpenApiTypes.OBJECT)
  75. def get_action_object(self, instance):
  76. context = {'request': self.context['request']}
  77. # We need to manually instantiate the serializer for scripts
  78. if instance.action_type == EventRuleActionChoices.SCRIPT:
  79. script = instance.action_object
  80. instance = script.python_class() if script.python_class else None
  81. return NestedScriptSerializer(instance, context=context).data
  82. else:
  83. serializer = get_serializer_for_model(
  84. model=instance.action_object_type.model_class(),
  85. prefix=NESTED_SERIALIZER_PREFIX
  86. )
  87. return serializer(instance.action_object, context=context).data
  88. #
  89. # Webhooks
  90. #
  91. class WebhookSerializer(NetBoxModelSerializer):
  92. url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail')
  93. class Meta:
  94. model = Webhook
  95. fields = [
  96. 'id', 'url', 'display', 'name', 'description', 'payload_url', 'http_method', 'http_content_type',
  97. 'additional_headers', 'body_template', 'secret', 'ssl_verification', 'ca_file_path', 'custom_fields',
  98. 'tags', 'created', 'last_updated',
  99. ]
  100. brief_fields = ('id', 'url', 'display', 'name', 'description')
  101. #
  102. # Custom fields
  103. #
  104. class CustomFieldSerializer(ValidatedModelSerializer):
  105. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
  106. object_types = ContentTypeField(
  107. queryset=ObjectType.objects.with_feature('custom_fields'),
  108. many=True
  109. )
  110. type = ChoiceField(choices=CustomFieldTypeChoices)
  111. object_type = ContentTypeField(
  112. queryset=ObjectType.objects.all(),
  113. required=False,
  114. allow_null=True
  115. )
  116. filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
  117. data_type = serializers.SerializerMethodField()
  118. choice_set = NestedCustomFieldChoiceSetSerializer(
  119. required=False,
  120. allow_null=True
  121. )
  122. ui_visible = ChoiceField(choices=CustomFieldUIVisibleChoices, required=False)
  123. ui_editable = ChoiceField(choices=CustomFieldUIEditableChoices, required=False)
  124. class Meta:
  125. model = CustomField
  126. fields = [
  127. 'id', 'url', 'display', 'object_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
  128. 'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
  129. 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
  130. 'created', 'last_updated',
  131. ]
  132. brief_fields = ('id', 'url', 'display', 'name', 'description')
  133. def validate_type(self, value):
  134. if self.instance and self.instance.type != value:
  135. raise serializers.ValidationError(_('Changing the type of custom fields is not supported.'))
  136. return value
  137. @extend_schema_field(OpenApiTypes.STR)
  138. def get_data_type(self, obj):
  139. types = CustomFieldTypeChoices
  140. if obj.type == types.TYPE_INTEGER:
  141. return 'integer'
  142. if obj.type == types.TYPE_DECIMAL:
  143. return 'decimal'
  144. if obj.type == types.TYPE_BOOLEAN:
  145. return 'boolean'
  146. if obj.type in (types.TYPE_JSON, types.TYPE_OBJECT):
  147. return 'object'
  148. if obj.type in (types.TYPE_MULTISELECT, types.TYPE_MULTIOBJECT):
  149. return 'array'
  150. return 'string'
  151. class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
  152. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfieldchoiceset-detail')
  153. base_choices = ChoiceField(
  154. choices=CustomFieldChoiceSetBaseChoices,
  155. required=False
  156. )
  157. extra_choices = serializers.ListField(
  158. child=serializers.ListField(
  159. min_length=2,
  160. max_length=2
  161. )
  162. )
  163. class Meta:
  164. model = CustomFieldChoiceSet
  165. fields = [
  166. 'id', 'url', 'display', 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically',
  167. 'choices_count', 'created', 'last_updated',
  168. ]
  169. brief_fields = ('id', 'url', 'display', 'name', 'description', 'choices_count')
  170. #
  171. # Custom links
  172. #
  173. class CustomLinkSerializer(ValidatedModelSerializer):
  174. url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail')
  175. content_types = ContentTypeField(
  176. queryset=ObjectType.objects.with_feature('custom_links'),
  177. many=True
  178. )
  179. class Meta:
  180. model = CustomLink
  181. fields = [
  182. 'id', 'url', 'display', 'content_types', 'name', 'enabled', 'link_text', 'link_url', 'weight', 'group_name',
  183. 'button_class', 'new_window', 'created', 'last_updated',
  184. ]
  185. brief_fields = ('id', 'url', 'display', 'name')
  186. #
  187. # Export templates
  188. #
  189. class ExportTemplateSerializer(ValidatedModelSerializer):
  190. url = serializers.HyperlinkedIdentityField(view_name='extras-api:exporttemplate-detail')
  191. content_types = ContentTypeField(
  192. queryset=ObjectType.objects.with_feature('export_templates'),
  193. many=True
  194. )
  195. data_source = NestedDataSourceSerializer(
  196. required=False
  197. )
  198. data_file = NestedDataFileSerializer(
  199. read_only=True
  200. )
  201. class Meta:
  202. model = ExportTemplate
  203. fields = [
  204. 'id', 'url', 'display', 'content_types', 'name', 'description', 'template_code', 'mime_type',
  205. 'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file', 'data_synced', 'created',
  206. 'last_updated',
  207. ]
  208. brief_fields = ('id', 'url', 'display', 'name', 'description')
  209. #
  210. # Saved filters
  211. #
  212. class SavedFilterSerializer(ValidatedModelSerializer):
  213. url = serializers.HyperlinkedIdentityField(view_name='extras-api:savedfilter-detail')
  214. content_types = ContentTypeField(
  215. queryset=ObjectType.objects.all(),
  216. many=True
  217. )
  218. class Meta:
  219. model = SavedFilter
  220. fields = [
  221. 'id', 'url', 'display', 'content_types', 'name', 'slug', 'description', 'user', 'weight', 'enabled',
  222. 'shared', 'parameters', 'created', 'last_updated',
  223. ]
  224. brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')
  225. #
  226. # Bookmarks
  227. #
  228. class BookmarkSerializer(ValidatedModelSerializer):
  229. url = serializers.HyperlinkedIdentityField(view_name='extras-api:bookmark-detail')
  230. object_type = ContentTypeField(
  231. queryset=ObjectType.objects.with_feature('bookmarks'),
  232. )
  233. object = serializers.SerializerMethodField(read_only=True)
  234. user = NestedUserSerializer()
  235. class Meta:
  236. model = Bookmark
  237. fields = [
  238. 'id', 'url', 'display', 'object_type', 'object_id', 'object', 'user', 'created',
  239. ]
  240. brief_fields = ('id', 'url', 'display', 'object_id', 'object_type')
  241. @extend_schema_field(serializers.JSONField(allow_null=True))
  242. def get_object(self, instance):
  243. serializer = get_serializer_for_model(instance.object, prefix=NESTED_SERIALIZER_PREFIX)
  244. return serializer(instance.object, context={'request': self.context['request']}).data
  245. #
  246. # Tags
  247. #
  248. class TagSerializer(ValidatedModelSerializer):
  249. url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
  250. object_types = ContentTypeField(
  251. queryset=ObjectType.objects.with_feature('tags'),
  252. many=True,
  253. required=False
  254. )
  255. # Related object counts
  256. tagged_items = RelatedObjectCountField('extras_taggeditem_items')
  257. class Meta:
  258. model = Tag
  259. fields = [
  260. 'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'object_types', 'tagged_items', 'created',
  261. 'last_updated',
  262. ]
  263. brief_fields = ('id', 'url', 'display', 'name', 'slug', 'color', 'description')
  264. #
  265. # Image attachments
  266. #
  267. class ImageAttachmentSerializer(ValidatedModelSerializer):
  268. url = serializers.HyperlinkedIdentityField(view_name='extras-api:imageattachment-detail')
  269. content_type = ContentTypeField(
  270. queryset=ObjectType.objects.all()
  271. )
  272. parent = serializers.SerializerMethodField(read_only=True)
  273. class Meta:
  274. model = ImageAttachment
  275. fields = [
  276. 'id', 'url', 'display', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height',
  277. 'image_width', 'created', 'last_updated',
  278. ]
  279. brief_fields = ('id', 'url', 'display', 'name', 'image')
  280. def validate(self, data):
  281. # Validate that the parent object exists
  282. try:
  283. data['content_type'].get_object_for_this_type(id=data['object_id'])
  284. except ObjectDoesNotExist:
  285. raise serializers.ValidationError(
  286. "Invalid parent object: {} ID {}".format(data['content_type'], data['object_id'])
  287. )
  288. # Enforce model validation
  289. super().validate(data)
  290. return data
  291. @extend_schema_field(serializers.JSONField(allow_null=True))
  292. def get_parent(self, obj):
  293. serializer = get_serializer_for_model(obj.parent, prefix=NESTED_SERIALIZER_PREFIX)
  294. return serializer(obj.parent, context={'request': self.context['request']}).data
  295. #
  296. # Journal entries
  297. #
  298. class JournalEntrySerializer(NetBoxModelSerializer):
  299. url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
  300. assigned_object_type = ContentTypeField(
  301. queryset=ObjectType.objects.all()
  302. )
  303. assigned_object = serializers.SerializerMethodField(read_only=True)
  304. created_by = serializers.PrimaryKeyRelatedField(
  305. allow_null=True,
  306. queryset=get_user_model().objects.all(),
  307. required=False,
  308. default=serializers.CurrentUserDefault()
  309. )
  310. kind = ChoiceField(
  311. choices=JournalEntryKindChoices,
  312. required=False
  313. )
  314. class Meta:
  315. model = JournalEntry
  316. fields = [
  317. 'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
  318. 'created_by', 'kind', 'comments', 'tags', 'custom_fields', 'last_updated',
  319. ]
  320. brief_fields = ('id', 'url', 'display', 'created')
  321. def validate(self, data):
  322. # Validate that the parent object exists
  323. if 'assigned_object_type' in data and 'assigned_object_id' in data:
  324. try:
  325. data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
  326. except ObjectDoesNotExist:
  327. raise serializers.ValidationError(
  328. f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
  329. )
  330. # Enforce model validation
  331. super().validate(data)
  332. return data
  333. @extend_schema_field(serializers.JSONField(allow_null=True))
  334. def get_assigned_object(self, instance):
  335. serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix=NESTED_SERIALIZER_PREFIX)
  336. context = {'request': self.context['request']}
  337. return serializer(instance.assigned_object, context=context).data
  338. #
  339. # Config contexts
  340. #
  341. class ConfigContextSerializer(ValidatedModelSerializer):
  342. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
  343. regions = SerializedPKRelatedField(
  344. queryset=Region.objects.all(),
  345. serializer=NestedRegionSerializer,
  346. required=False,
  347. many=True
  348. )
  349. site_groups = SerializedPKRelatedField(
  350. queryset=SiteGroup.objects.all(),
  351. serializer=NestedSiteGroupSerializer,
  352. required=False,
  353. many=True
  354. )
  355. sites = SerializedPKRelatedField(
  356. queryset=Site.objects.all(),
  357. serializer=NestedSiteSerializer,
  358. required=False,
  359. many=True
  360. )
  361. locations = SerializedPKRelatedField(
  362. queryset=Location.objects.all(),
  363. serializer=NestedLocationSerializer,
  364. required=False,
  365. many=True
  366. )
  367. device_types = SerializedPKRelatedField(
  368. queryset=DeviceType.objects.all(),
  369. serializer=NestedDeviceTypeSerializer,
  370. required=False,
  371. many=True
  372. )
  373. roles = SerializedPKRelatedField(
  374. queryset=DeviceRole.objects.all(),
  375. serializer=NestedDeviceRoleSerializer,
  376. required=False,
  377. many=True
  378. )
  379. platforms = SerializedPKRelatedField(
  380. queryset=Platform.objects.all(),
  381. serializer=NestedPlatformSerializer,
  382. required=False,
  383. many=True
  384. )
  385. cluster_types = SerializedPKRelatedField(
  386. queryset=ClusterType.objects.all(),
  387. serializer=NestedClusterTypeSerializer,
  388. required=False,
  389. many=True
  390. )
  391. cluster_groups = SerializedPKRelatedField(
  392. queryset=ClusterGroup.objects.all(),
  393. serializer=NestedClusterGroupSerializer,
  394. required=False,
  395. many=True
  396. )
  397. clusters = SerializedPKRelatedField(
  398. queryset=Cluster.objects.all(),
  399. serializer=NestedClusterSerializer,
  400. required=False,
  401. many=True
  402. )
  403. tenant_groups = SerializedPKRelatedField(
  404. queryset=TenantGroup.objects.all(),
  405. serializer=NestedTenantGroupSerializer,
  406. required=False,
  407. many=True
  408. )
  409. tenants = SerializedPKRelatedField(
  410. queryset=Tenant.objects.all(),
  411. serializer=NestedTenantSerializer,
  412. required=False,
  413. many=True
  414. )
  415. tags = serializers.SlugRelatedField(
  416. queryset=Tag.objects.all(),
  417. slug_field='slug',
  418. required=False,
  419. many=True
  420. )
  421. data_source = NestedDataSourceSerializer(
  422. required=False
  423. )
  424. data_file = NestedDataFileSerializer(
  425. read_only=True
  426. )
  427. class Meta:
  428. model = ConfigContext
  429. fields = [
  430. 'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
  431. 'locations', 'device_types', 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters',
  432. 'tenant_groups', 'tenants', 'tags', 'data_source', 'data_path', 'data_file', 'data_synced', 'data',
  433. 'created', 'last_updated',
  434. ]
  435. brief_fields = ('id', 'url', 'display', 'name', 'description')
  436. #
  437. # Config templates
  438. #
  439. class ConfigTemplateSerializer(TaggableModelSerializer, ValidatedModelSerializer):
  440. url = serializers.HyperlinkedIdentityField(view_name='extras-api:configtemplate-detail')
  441. data_source = NestedDataSourceSerializer(
  442. required=False
  443. )
  444. data_file = NestedDataFileSerializer(
  445. required=False
  446. )
  447. class Meta:
  448. model = ConfigTemplate
  449. fields = [
  450. 'id', 'url', 'display', 'name', 'description', 'environment_params', 'template_code', 'data_source',
  451. 'data_path', 'data_file', 'data_synced', 'tags', 'created', 'last_updated',
  452. ]
  453. brief_fields = ('id', 'url', 'display', 'name', 'description')
  454. #
  455. # Scripts
  456. #
  457. class ScriptSerializer(ValidatedModelSerializer):
  458. url = serializers.HyperlinkedIdentityField(view_name='extras-api:script-detail')
  459. description = serializers.SerializerMethodField(read_only=True)
  460. vars = serializers.SerializerMethodField(read_only=True)
  461. result = NestedJobSerializer(read_only=True)
  462. class Meta:
  463. model = Script
  464. fields = [
  465. 'id', 'url', 'module', 'name', 'description', 'vars', 'result', 'display', 'is_executable',
  466. ]
  467. brief_fields = ('id', 'url', 'display', 'name', 'description')
  468. @extend_schema_field(serializers.JSONField(allow_null=True))
  469. def get_vars(self, obj):
  470. if obj.python_class:
  471. return {
  472. k: v.__class__.__name__ for k, v in obj.python_class()._get_vars().items()
  473. }
  474. else:
  475. return {}
  476. @extend_schema_field(serializers.CharField())
  477. def get_display(self, obj):
  478. return f'{obj.name} ({obj.module})'
  479. @extend_schema_field(serializers.CharField())
  480. def get_description(self, obj):
  481. if obj.python_class:
  482. return obj.python_class().description
  483. else:
  484. return None
  485. class ScriptDetailSerializer(ScriptSerializer):
  486. result = serializers.SerializerMethodField(read_only=True)
  487. @extend_schema_field(JobSerializer())
  488. def get_result(self, obj):
  489. job = obj.jobs.all().order_by('-created').first()
  490. context = {
  491. 'request': self.context['request']
  492. }
  493. data = JobSerializer(job, context=context).data
  494. return data
  495. class ScriptInputSerializer(serializers.Serializer):
  496. data = serializers.JSONField()
  497. commit = serializers.BooleanField()
  498. schedule_at = serializers.DateTimeField(required=False, allow_null=True)
  499. interval = serializers.IntegerField(required=False, allow_null=True)
  500. def validate_schedule_at(self, value):
  501. if value and not self.context['script'].scheduling_enabled:
  502. raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
  503. return value
  504. def validate_interval(self, value):
  505. if value and not self.context['script'].scheduling_enabled:
  506. raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
  507. return value
  508. #
  509. # Change logging
  510. #
  511. class ObjectChangeSerializer(BaseModelSerializer):
  512. url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
  513. user = NestedUserSerializer(
  514. read_only=True
  515. )
  516. action = ChoiceField(
  517. choices=ObjectChangeActionChoices,
  518. read_only=True
  519. )
  520. changed_object_type = ContentTypeField(
  521. read_only=True
  522. )
  523. changed_object = serializers.SerializerMethodField(
  524. read_only=True
  525. )
  526. class Meta:
  527. model = ObjectChange
  528. fields = [
  529. 'id', 'url', 'display', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
  530. 'changed_object_id', 'changed_object', 'prechange_data', 'postchange_data',
  531. ]
  532. @extend_schema_field(serializers.JSONField(allow_null=True))
  533. def get_changed_object(self, obj):
  534. """
  535. Serialize a nested representation of the changed object.
  536. """
  537. if obj.changed_object is None:
  538. return None
  539. try:
  540. serializer = get_serializer_for_model(obj.changed_object, prefix=NESTED_SERIALIZER_PREFIX)
  541. except SerializerNotFound:
  542. return obj.object_repr
  543. context = {
  544. 'request': self.context['request']
  545. }
  546. data = serializer(obj.changed_object, context=context).data
  547. return data
  548. #
  549. # ContentTypes
  550. #
  551. class ContentTypeSerializer(BaseModelSerializer):
  552. url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
  553. class Meta:
  554. model = ObjectType
  555. fields = ['id', 'url', 'display', 'app_label', 'model']
  556. #
  557. # User dashboard
  558. #
  559. class DashboardSerializer(serializers.ModelSerializer):
  560. class Meta:
  561. model = Dashboard
  562. fields = ('layout', 'config')