Przeglądaj źródła

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 <noreply@anthropic.com>
Jeremy Stretch 9 godzin temu
rodzic
commit
443a22706f

+ 4 - 2
netbox/core/api/schema.py

@@ -215,8 +215,10 @@ class NetBoxAutoSchema(AutoSchema):
     def _get_serializer_name(self, serializer, direction, bypass_extensions=False) -> str:
     def _get_serializer_name(self, serializer, direction, bypass_extensions=False) -> str:
         name = super()._get_serializer_name(serializer, direction, bypass_extensions)
         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}'
             name = f'Brief{name}'
 
 
         return name
         return name

+ 23 - 1
netbox/core/tests/test_openapi_schema.py

@@ -131,7 +131,7 @@ class OpenAPISchemaTestCase(TestCase):
         for component, field, ref in (
         for component, field, ref in (
             ('Site', 'asns', 'BriefASN'),
             ('Site', 'asns', 'BriefASN'),
             ('ConfigContext', 'sites', 'BriefSite'),
             ('ConfigContext', 'sites', 'BriefSite'),
-            ('ASN', 'sites', 'BriefASNSite'),
+            ('Interface', 'tagged_vlans', 'BriefVLAN'),
         ):
         ):
             with self.subTest(component=component, field=field):
             with self.subTest(component=component, field=field):
                 self.assertEqual(
                 self.assertEqual(
@@ -145,6 +145,28 @@ class OpenAPISchemaTestCase(TestCase):
             {'id', 'url', 'display', 'asn', 'description'}
             {'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):
     def test_non_nested_related_fields_reference_full_components(self):
         """
         """
         A SerializedPKRelatedField declared without nested=True must continue to reference the
         A SerializedPKRelatedField declared without nested=True must continue to reference the

+ 1 - 0
netbox/ipam/api/serializers_/asns.py

@@ -54,6 +54,7 @@ class ASNSiteSerializer(PrimaryModelSerializer):
         model = Site
         model = Site
         fields = ('id', 'url', 'display', 'name', 'description', 'slug')
         fields = ('id', 'url', 'display', 'name', 'description', 'slug')
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug')
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug')
+        ref_name = 'ASNSite'
 
 
 
 
 class ASNSerializer(PrimaryModelSerializer):
 class ASNSerializer(PrimaryModelSerializer):

+ 2 - 0
netbox/users/api/serializers_/nested.py

@@ -15,6 +15,7 @@ class NestedGroupSerializer(WritableNestedSerializer):
     class Meta:
     class Meta:
         model = models.Group
         model = models.Group
         fields = ['id', 'url', 'display_url', 'display', 'name']
         fields = ['id', 'url', 'display_url', 'display', 'name']
+        ref_name = 'NestedGroup'
 
 
 
 
 class NestedUserSerializer(WritableNestedSerializer):
 class NestedUserSerializer(WritableNestedSerializer):
@@ -22,6 +23,7 @@ class NestedUserSerializer(WritableNestedSerializer):
     class Meta:
     class Meta:
         model = models.User
         model = models.User
         fields = ['id', 'url', 'display_url', 'display', 'username']
         fields = ['id', 'url', 'display_url', 'display', 'username']
+        ref_name = 'NestedUser'
 
 
     @extend_schema_field(OpenApiTypes.STR)
     @extend_schema_field(OpenApiTypes.STR)
     def get_display(self, obj):
     def get_display(self, obj):