Fixes #22154: Correct OpenAPI schema regarding relation counts for nested objects
This commit is contained in:
parent
614d50a564
commit
7a1dfb58e3
|
|
@ -4,6 +4,7 @@ from drf_spectacular.types import OpenApiTypes
|
|||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from netbox.api.fields import RelatedObjectCountField
|
||||
from utilities.api import get_related_object_by_attrs
|
||||
|
||||
from .fields import NetBoxAPIHyperlinkedIdentityField, NetBoxURLHyperlinkedIdentityField
|
||||
|
|
@ -69,6 +70,15 @@ class BaseModelSerializer(serializers.ModelSerializer):
|
|||
for field_name in set(self._omit_fields):
|
||||
fields.pop(field_name, None)
|
||||
|
||||
# Related object counts are populated by annotations applied to the viewset's queryset, but these
|
||||
# annotations are not applied when the object is represented as a nested (brief) related object. Omit
|
||||
# these fields when serializing a nested object to avoid advertising fields that will never be populated
|
||||
# (and which would otherwise be declared as required in the generated OpenAPI schema). See #22154.
|
||||
if self.nested:
|
||||
for field_name, field in list(fields.items()):
|
||||
if isinstance(field, RelatedObjectCountField):
|
||||
fields.pop(field_name)
|
||||
|
||||
return fields
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ from django.urls import reverse
|
|||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.request import Request
|
||||
|
||||
from dcim.api.serializers import RackSerializer
|
||||
from netbox.api.exceptions import QuerySetNotOrdered
|
||||
from netbox.api.fields import IntegerRangeSerializer
|
||||
from netbox.api.fields import IntegerRangeSerializer, RelatedObjectCountField
|
||||
from netbox.api.pagination import NetBoxPagination
|
||||
from users.models import Token
|
||||
from utilities.testing import APITestCase
|
||||
|
|
@ -48,6 +49,29 @@ class AppTestCase(APITestCase):
|
|||
self.assertEqual(response.data['id'], self.user.pk)
|
||||
|
||||
|
||||
class RelatedObjectCountFieldTestCase(TestCase):
|
||||
"""
|
||||
RelatedObjectCountFields are populated by annotations applied to a viewset's queryset, which are only
|
||||
added when serializing an object via its own endpoint (including ?brief=1). They are never annotated when
|
||||
the object is rendered as a nested related object, so they must be omitted from nested representations to
|
||||
keep the generated OpenAPI schema honest. See #22154.
|
||||
"""
|
||||
def test_count_field_omitted_when_nested(self):
|
||||
"""A nested serializer must drop RelatedObjectCountFields (e.g. RackSerializer.device_count)."""
|
||||
serializer = RackSerializer(nested=True)
|
||||
count_fields = [
|
||||
name for name, field in serializer.fields.items() if isinstance(field, RelatedObjectCountField)
|
||||
]
|
||||
self.assertEqual(count_fields, [])
|
||||
self.assertNotIn('device_count', serializer.fields)
|
||||
|
||||
def test_count_field_retained_in_brief_mode(self):
|
||||
"""?brief=1 (fields=brief_fields, not nested) must retain RelatedObjectCountFields."""
|
||||
serializer = RackSerializer(fields=RackSerializer.Meta.brief_fields)
|
||||
self.assertIn('device_count', serializer.fields)
|
||||
self.assertIsInstance(serializer.fields['device_count'], RelatedObjectCountField)
|
||||
|
||||
|
||||
class OptionalLimitOffsetPaginationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
Loading…
Reference in New Issue