From 3c8cafe6e0b34256bc3d00d62c4124f18225851b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Jul 2025 16:11:29 -0400 Subject: [PATCH] Closes #19722: Extend the object types REST API endpoint --- netbox/core/models/contenttypes.py | 29 +++++++++++++++++++ netbox/extras/api/serializers_/objecttypes.py | 29 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/netbox/core/models/contenttypes.py b/netbox/core/models/contenttypes.py index b0301848f..2a65c523a 100644 --- a/netbox/core/models/contenttypes.py +++ b/netbox/core/models/contenttypes.py @@ -1,7 +1,9 @@ from django.contrib.contenttypes.models import ContentType, ContentTypeManager from django.db.models import Q +from netbox.plugins import PluginConfig from netbox.registry import registry +from utilities.string import title __all__ = ( 'ObjectType', @@ -48,3 +50,30 @@ class ObjectType(ContentType): class Meta: proxy = True + + @property + def app_labeled_name(self): + # Override ContentType's "app | model" representation style. + return f"{self.app_verbose_name} > {title(self.model_verbose_name)}" + + @property + def app_verbose_name(self): + if model := self.model_class(): + return model._meta.app_config.verbose_name + + @property + def model_verbose_name(self): + if model := self.model_class(): + return model._meta.verbose_name + + @property + def model_verbose_name_plural(self): + if model := self.model_class(): + return model._meta.verbose_name_plural + return model._meta.label + + @property + def is_plugin_model(self): + if not (model := self.model_class()): + return # Return null if model class is invalid + return isinstance(model._meta.app_config, PluginConfig) diff --git a/netbox/extras/api/serializers_/objecttypes.py b/netbox/extras/api/serializers_/objecttypes.py index 8e4806652..61c6844c3 100644 --- a/netbox/extras/api/serializers_/objecttypes.py +++ b/netbox/extras/api/serializers_/objecttypes.py @@ -1,7 +1,11 @@ +import inspect + +from django.urls import NoReverseMatch, reverse from rest_framework import serializers from core.models import ObjectType from netbox.api.serializers import BaseModelSerializer +from utilities.views import get_viewname __all__ = ( 'ObjectTypeSerializer', @@ -10,7 +14,30 @@ __all__ = ( class ObjectTypeSerializer(BaseModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='extras-api:objecttype-detail') + app_name = serializers.CharField(source='app_verbose_name', read_only=True) + model_name = serializers.CharField(source='model_verbose_name', read_only=True) + model_name_plural = serializers.CharField(source='model_verbose_name_plural', read_only=True) + is_plugin_model = serializers.BooleanField(read_only=True) + rest_api_endpoint = serializers.SerializerMethodField() + description = serializers.SerializerMethodField() class Meta: model = ObjectType - fields = ['id', 'url', 'display', 'app_label', 'model'] + fields = [ + 'id', 'url', 'display', 'app_label', 'app_name', 'model', 'model_name', 'model_name_plural', + 'is_plugin_model', 'rest_api_endpoint', 'description', + ] + + def get_rest_api_endpoint(self, obj): + if not (model := obj.model_class()): + return + if viewname := get_viewname(model, action='list', rest_api=True): + try: + return reverse(viewname) + except NoReverseMatch: + return + + def get_description(self, obj): + if not (model := obj.model_class()): + return + return inspect.getdoc(model)