From fa5f9430fc5fdd92e2053abc10e2943af8f23963 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 5 Mar 2026 11:10:49 -0500 Subject: [PATCH] Fixes #20468: Fix range lookups for numeric GraphQL filters (#21589) * Fixes #20468: Fix range lookups for numeric GraphQL filters * Update netbox/netbox/tests/test_graphql.py --------- Co-authored-by: Martin Hauser --- netbox/netbox/graphql/filter_lookups.py | 9 +++++++ netbox/netbox/tests/test_graphql.py | 36 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/graphql/filter_lookups.py b/netbox/netbox/graphql/filter_lookups.py index 6dbf76263..583caa6d3 100644 --- a/netbox/netbox/graphql/filter_lookups.py +++ b/netbox/netbox/graphql/filter_lookups.py @@ -79,6 +79,9 @@ class IntegerLookup: if not filters: return queryset, Q() + if isinstance(filters, RangeLookup): + prefix = f'{prefix}range__' + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) @@ -102,6 +105,9 @@ class BigIntegerLookup: if not filters: return queryset, Q() + if isinstance(filters, RangeLookup): + prefix = f'{prefix}range__' + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) @@ -125,6 +131,9 @@ class FloatLookup: if not filters: return queryset, Q() + if isinstance(filters, RangeLookup): + prefix = f'{prefix}range__' + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index 1c542000e..cdf5cf3b5 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -5,7 +5,7 @@ from django.urls import reverse from rest_framework import status from dcim.choices import LocationStatusChoices -from dcim.models import Location, Site +from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis from utilities.testing import APITestCase, TestCase, disable_warnings @@ -138,6 +138,40 @@ class GraphQLAPITestCase(APITestCase): self.assertNotIn('errors', data) self.assertEqual(len(data['data']['site']['locations']), 0) + def test_graphql_integer_range_lookup(self): + """ + Test that range_lookup works for integer fields (e.g. vc_position). Regression test for #20468. + """ + self.add_permissions('dcim.view_device') + url = reverse('graphql') + + manufacturer = Manufacturer.objects.create(name='Test Manufacturer', slug='test-manufacturer') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device', slug='test-device') + device_role = DeviceRole.objects.create(name='Test Role', slug='test-role') + site = Site.objects.first() + vc = VirtualChassis.objects.create(name='Test VC') + + devices = [ + Device(name=f'Device {i}', device_type=device_type, role=device_role, site=site, + virtual_chassis=vc, vc_position=i) + for i in range(1, 6) + ] + Device.objects.bulk_create(devices) + + # range_lookup should return devices with vc_position between 2 and 4 inclusive + query = """ + { + device_list(filters: {vc_position: {range_lookup: {start: 2, end: 4}}}) { + id name + } + } + """ + 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']['device_list']), 3) + def test_offset_pagination(self): self.add_permissions('dcim.view_site') url = reverse('graphql')