From 443a22706f4ff81c218ff8ef0363e914a8cfa2fd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Aug 2026 15:55:51 -0400 Subject: [PATCH] Avoid renaming existing schema components Serializers used only in a nested context have no complete form in the schema, so prefixing them with "Brief" renamed an existing component to no purpose and dropped the old name entirely. Exempt serializers declaring an explicit Meta.ref_name from the prefix, and pin the three affected names. This narrows the schema diff to the fields the bug actually affected: no components are removed, and the nine which are added are purely additive. Co-Authored-By: Claude Opus 5 --- netbox/core/api/schema.py | 6 ++++-- netbox/core/tests/test_openapi_schema.py | 24 +++++++++++++++++++++++- netbox/ipam/api/serializers_/asns.py | 1 + netbox/users/api/serializers_/nested.py | 2 ++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py index 57dc842b7..939868e40 100644 --- a/netbox/core/api/schema.py +++ b/netbox/core/api/schema.py @@ -215,8 +215,10 @@ class NetBoxAutoSchema(AutoSchema): def _get_serializer_name(self, serializer, direction, bypass_extensions=False) -> str: name = super()._get_serializer_name(serializer, direction, bypass_extensions) - # If this serializer is nested, prepend its name with "Brief" - if getattr(serializer, 'nested', False): + # If this serializer is nested, prepend its name with "Brief". Serializers which declare an explicit + # Meta.ref_name are exempt: those are brief by design and have no complete form in the schema, so the + # prefix would only rename an existing component to no purpose. See #22989. + if getattr(serializer, 'nested', False) and not getattr(getattr(serializer, 'Meta', None), 'ref_name', None): name = f'Brief{name}' return name diff --git a/netbox/core/tests/test_openapi_schema.py b/netbox/core/tests/test_openapi_schema.py index 00da4ae3c..50ac6c970 100644 --- a/netbox/core/tests/test_openapi_schema.py +++ b/netbox/core/tests/test_openapi_schema.py @@ -131,7 +131,7 @@ class OpenAPISchemaTestCase(TestCase): for component, field, ref in ( ('Site', 'asns', 'BriefASN'), ('ConfigContext', 'sites', 'BriefSite'), - ('ASN', 'sites', 'BriefASNSite'), + ('Interface', 'tagged_vlans', 'BriefVLAN'), ): with self.subTest(component=component, field=field): self.assertEqual( @@ -145,6 +145,28 @@ class OpenAPISchemaTestCase(TestCase): {'id', 'url', 'display', 'asn', 'description'} ) + def test_ref_name_exempts_serializer_from_brief_prefix(self): + """ + A serializer which declares an explicit Meta.ref_name keeps that name when nested, rather than + acquiring a Brief prefix. These serializers are brief by design and have no complete form in the + schema, so prefixing them would rename an existing component to no purpose. + + Refs: #22989 + """ + components = self.schema['components']['schemas'] + + for component, field, ref in ( + ('ASN', 'sites', 'ASNSite'), + ('ObjectPermission', 'groups', 'NestedGroup'), + ('ObjectPermission', 'users', 'NestedUser'), + ): + with self.subTest(component=component, field=field): + self.assertEqual( + components[component]['properties'][field]['items']['$ref'], + f'#/components/schemas/{ref}' + ) + self.assertNotIn(f'Brief{ref}', components) + def test_non_nested_related_fields_reference_full_components(self): """ A SerializedPKRelatedField declared without nested=True must continue to reference the diff --git a/netbox/ipam/api/serializers_/asns.py b/netbox/ipam/api/serializers_/asns.py index c5c95bf19..fe7153cf3 100644 --- a/netbox/ipam/api/serializers_/asns.py +++ b/netbox/ipam/api/serializers_/asns.py @@ -54,6 +54,7 @@ class ASNSiteSerializer(PrimaryModelSerializer): model = Site fields = ('id', 'url', 'display', 'name', 'description', 'slug') brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug') + ref_name = 'ASNSite' class ASNSerializer(PrimaryModelSerializer): diff --git a/netbox/users/api/serializers_/nested.py b/netbox/users/api/serializers_/nested.py index b268776b5..a3e9ffaa2 100644 --- a/netbox/users/api/serializers_/nested.py +++ b/netbox/users/api/serializers_/nested.py @@ -15,6 +15,7 @@ class NestedGroupSerializer(WritableNestedSerializer): class Meta: model = models.Group fields = ['id', 'url', 'display_url', 'display', 'name'] + ref_name = 'NestedGroup' class NestedUserSerializer(WritableNestedSerializer): @@ -22,6 +23,7 @@ class NestedUserSerializer(WritableNestedSerializer): class Meta: model = models.User fields = ['id', 'url', 'display_url', 'display', 'username'] + ref_name = 'NestedUser' @extend_schema_field(OpenApiTypes.STR) def get_display(self, obj):