From 9c3fb57a93b3f9ad9998bb21ff0aef5a56ef74bf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Jul 2026 10:41:31 -0400 Subject: [PATCH] Fixes #22568: Fix ValueError exception when receiving an invalid `filter_id` value (#22602) --- netbox/netbox/filtersets.py | 11 ++++++- netbox/utilities/tests/test_filters.py | 44 +++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index 14ef4d158..3ef120fa5 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -108,9 +108,18 @@ class BaseFilterSet(django_filters.FilterSet): # Apply any referenced SavedFilters if data and ('filter' in data or 'filter_id' in data): data = data.copy() # Get a mutable copy + + # Coerce filter_id values to integers, ignoring any which are not valid (see #22568) + filter_ids = [] + for f_id in data.pop('filter_id', []): + try: + filter_ids.append(int(f_id)) + except (ValueError, TypeError): + pass + saved_filters = SavedFilter.objects.filter( Q(slug__in=data.pop('filter', [])) | - Q(pk__in=data.pop('filter_id', [])) + Q(pk__in=filter_ids) ) for sf in saved_filters: for key, value in sf.parameters.items(): diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 591934aee..6d960fe0b 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -1,10 +1,12 @@ import django_filters from django.conf import settings from django.db import models +from django.http import QueryDict from django.test import TestCase from mptt.fields import TreeForeignKey from taggit.managers import TaggableManager +from core.models import ObjectType from dcim.choices import * from dcim.fields import MACAddressField from dcim.filtersets import DeviceFilterSet, InterfaceFilterSet, SiteFilterSet @@ -21,7 +23,7 @@ from dcim.models import ( Site, ) from extras.filters import TagFilter -from extras.models import TaggedItem +from extras.models import SavedFilter, TaggedItem from ipam.filtersets import ASNFilterSet from ipam.models import ASN, RIR from netbox.filtersets import BaseFilterSet @@ -670,3 +672,43 @@ class DynamicFilterLookupExpressionTestCase(TestCase): self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5) params = {'rf_role__empty': 'false'} self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 1) + + +class SavedFilterApplicationTestCase(TestCase): + """ + Validate the application of referenced SavedFilters to a FilterSet's data. + """ + @classmethod + def setUpTestData(cls): + sites = ( + Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE), + Site(name='Site 2', slug='site-2', status=SiteStatusChoices.STATUS_PLANNED), + ) + Site.objects.bulk_create(sites) + + cls.saved_filter = SavedFilter.objects.create( + name='Active Sites', + slug='active-sites', + parameters={'status': [SiteStatusChoices.STATUS_ACTIVE]}, + ) + cls.saved_filter.object_types.set([ObjectType.objects.get_for_model(Site)]) + + def test_filter_id_valid(self): + # A referenced SavedFilter's parameters are applied to the queryset + data = QueryDict(f'filter_id={self.saved_filter.pk}') + self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 1) + + def test_filter_id_nonexistent(self): + # A non-existent (but valid integer) filter_id is ignored + data = QueryDict('filter_id=999999') + self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 2) + + def test_filter_id_non_integer(self): + # A non-integer filter_id is ignored rather than raising a ValueError (#22568) + data = QueryDict('filter_id=abc') + self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 2) + + def test_filter_slug(self): + # A referenced SavedFilter may also be applied by slug + data = QueryDict('filter=active-sites') + self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 1)