From 6bfe1e7b894b77d64e3ff0f8ff8d676a49971793 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 11 Feb 2026 16:15:56 -0500 Subject: [PATCH] Fixes #21196: q filter should match on primary IP only for IP address values --- netbox/dcim/filtersets.py | 20 +++++++++++++++----- netbox/virtualization/filtersets.py | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index e11d32d95..acc117398 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1,8 +1,10 @@ import django_filters +import netaddr from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field +from netaddr.core import AddrFormatError from circuits.models import CircuitTermination, VirtualCircuit, VirtualCircuitTermination from extras.filtersets import LocalConfigContextFilterSet @@ -1329,16 +1331,24 @@ class DeviceFilterSet( def search(self, queryset, name, value): if not value.strip(): return queryset - return queryset.filter( + qs_filter = ( Q(name__icontains=value) | Q(virtual_chassis__name__icontains=value) | Q(serial__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) | Q(description__icontains=value.strip()) | - Q(comments__icontains=value) | - Q(primary_ip4__address__startswith=value) | - Q(primary_ip6__address__startswith=value) - ).distinct() + Q(comments__icontains=value) + ) + # If the given value looks like an IP address, look for primary IPv4/IPv6 assignments + try: + ipaddress = netaddr.IPNetwork(value) + if ipaddress.version == 4: + qs_filter |= Q(primary_ip4__address__host__inet=ipaddress.ip) + elif ipaddress.version == 6: + qs_filter |= Q(primary_ip6__address__host__inet=ipaddress.ip) + except (AddrFormatError, ValueError): + pass + return queryset.filter(qs_filter) def _has_primary_ip(self, queryset, name, value): params = Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False) diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 2c4521cfd..534cabdd1 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -1,6 +1,8 @@ import django_filters +import netaddr from django.db.models import Q from django.utils.translation import gettext as _ +from netaddr.core import AddrFormatError from dcim.base_filtersets import ScopedFilterSet from dcim.filtersets import CommonInterfaceFilterSet @@ -229,14 +231,22 @@ class VirtualMachineFilterSet( def search(self, queryset, name, value): if not value.strip(): return queryset - return queryset.filter( + qs_filter = queryset.filter( Q(name__icontains=value) | Q(description__icontains=value) | Q(comments__icontains=value) | - Q(primary_ip4__address__startswith=value) | - Q(primary_ip6__address__startswith=value) | Q(serial__icontains=value) ) + # If the given value looks like an IP address, look for primary IPv4/IPv6 assignments + try: + ipaddress = netaddr.IPNetwork(value) + if ipaddress.version == 4: + qs_filter |= Q(primary_ip4__address__host__inet=ipaddress.ip) + elif ipaddress.version == 6: + qs_filter |= Q(primary_ip6__address__host__inet=ipaddress.ip) + except (AddrFormatError, ValueError): + pass + return qs_filter def _has_primary_ip(self, queryset, name, value): params = Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False)