Просмотр исходного кода

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 10 часов назад
Родитель
Сommit
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:
         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

+ 23 - 1
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

+ 1 - 0
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):

+ 2 - 0
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):