22766 - Map GraphQL array length lookup to Django's len transform

This commit is contained in:
Arthur 2026-07-24 17:26:15 -07:00
parent ba5018a3b4
commit f8e7950004
2 changed files with 56 additions and 5 deletions

View File

@ -306,10 +306,24 @@ class ArrayLookup(Generic[T]):
Class for Array field lookups
"""
contains: list[T] | None = strawberry_django.filter_field(description='Contains the value')
contained_by: list[T] | None = strawberry_django.filter_field(description='Contained by the value')
overlap: list[T] | None = strawberry_django.filter_field(description='Overlaps with the value')
length: int | None = strawberry_django.filter_field(description='Length of the array')
contains: list[T] | None = strawberry.field(default=strawberry.UNSET, description='Contains the value')
contained_by: list[T] | None = strawberry.field(default=strawberry.UNSET, description='Contained by the value')
overlap: list[T] | None = strawberry.field(default=strawberry.UNSET, description='Overlaps with the value')
length: int | None = strawberry.field(default=strawberry.UNSET, description='Length of the array')
@strawberry_django.filter_field
def filter(self, info: Info, queryset: QuerySet, prefix: str = '') -> tuple[QuerySet, Q]:
# Map the public GraphQL ``length`` field to Django's ``len`` array transform; the
# remaining lookups share their name with the corresponding ORM transform.
if self.contains is not strawberry.UNSET and self.contains is not None:
return queryset, Q(**{f'{prefix}contains': self.contains})
if self.contained_by is not strawberry.UNSET and self.contained_by is not None:
return queryset, Q(**{f'{prefix}contained_by': self.contained_by})
if self.overlap is not strawberry.UNSET and self.overlap is not None:
return queryset, Q(**{f'{prefix}overlap': self.overlap})
if self.length is not strawberry.UNSET and self.length is not None:
return queryset, Q(**{f'{prefix}len': self.length})
return queryset, Q()
@strawberry.input(one_of=True, description='Lookup for Array fields. Only one of the lookup fields can be set.')

View File

@ -12,7 +12,17 @@ from strawberry.extensions import QueryDepthLimiter
from strawberry.schema.config import StrawberryConfig
from dcim.choices import LocationStatusChoices
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis
from dcim.models import (
Device,
DeviceRole,
DeviceType,
Location,
Manufacturer,
Rack,
RackReservation,
Site,
VirtualChassis,
)
from extras.models import TableConfig, Tag
from netbox.graphql.scalars import BigInt, BigIntScalar
from netbox.graphql.schema import Query, get_schema_extensions, schema
@ -315,6 +325,33 @@ class GraphQLAPITestCase(APITestCase):
self.assertNotIn('errors', data)
self.assertEqual(len(data['data']['device_list']), 3)
def test_graphql_array_length_lookup(self):
"""
The public GraphQL ``length`` array lookup must map to Django's ``len`` transform.
Regression test for #22766 using a standard array field (RackReservation.units).
"""
self.add_permissions('dcim.view_rackreservation')
url = reverse('graphql')
site = Site.objects.first()
rack = Rack.objects.create(name='Reservation Rack', site=site)
RackReservation.objects.create(rack=rack, units=[1, 2], user=self.user, description='Two units')
RackReservation.objects.create(rack=rack, units=[3, 4, 5], user=self.user, description='Three units')
query = """
{
rack_reservation_list(filters: {units: {length: 2}}) {
id units
}
}
"""
response = self.client.post(url, data={'query': query}, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
data = json.loads(response.content)
self.assertNotIn('errors', data)
self.assertEqual(len(data['data']['rack_reservation_list']), 1)
self.assertEqual(data['data']['rack_reservation_list'][0]['units'], [1, 2])
def test_graphql_tableconfig_object_type_exposes_id(self):
"""TableConfigType.object_type must expose ContentType fields (e.g. id)."""
self.add_permissions('extras.view_tableconfig')