From cb5bf5dbbd40b52a1856eab5a6c75c9fbf0e0b9e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 10 Jul 2025 16:14:18 -0400 Subject: [PATCH 1/2] Closes #18811: Match full-form IPv6 addresses in global search --- netbox/extras/lookups.py | 16 +++++++++++++++- netbox/ipam/models/ip.py | 15 +++++++++++++++ netbox/netbox/search/backends.py | 8 +++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/netbox/extras/lookups.py b/netbox/extras/lookups.py index c496cce78b9..9e1fe4a0b6e 100644 --- a/netbox/extras/lookups.py +++ b/netbox/extras/lookups.py @@ -18,9 +18,22 @@ def as_sql(self, compiler, connection): return f"CAST(LENGTH({sql}) AS BOOLEAN) IS TRUE", params +class NetHost(Lookup): + """ + Similar to ipam.lookups.NetHost, but casts the field to INET. + """ + lookup_name = 'net_host' + + def as_sql(self, qn, connection): + lhs, lhs_params = self.process_lhs(qn, connection) + rhs, rhs_params = self.process_rhs(qn, connection) + params = lhs_params + rhs_params + return 'HOST(CAST(%s AS INET)) = HOST(%s)' % (lhs, rhs), params + + class NetContainsOrEquals(Lookup): """ - This lookup has the same functionality as the one from the ipam app except lhs is cast to inet + Similar to ipam.lookups.NetContainsOrEquals, but casts the field to INET. """ lookup_name = 'net_contains_or_equals' @@ -32,4 +45,5 @@ def as_sql(self, qn, connection): CharField.register_lookup(Empty) +CachedValueField.register_lookup(NetHost) CachedValueField.register_lookup(NetContainsOrEquals) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index ab2481d9022..db116e9e493 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -162,6 +162,11 @@ def family(self): return self.prefix.version return None + @property + def ipv6_full(self): + if self.prefix and self.prefix.version == 6: + return netaddr.IPAddress(self.prefix).format(netaddr.ipv6_full) + def get_child_prefixes(self): """ Return all Prefixes within this Aggregate @@ -330,6 +335,11 @@ def family(self): def mask_length(self): return self.prefix.prefixlen if self.prefix else None + @property + def ipv6_full(self): + if self.prefix and self.prefix.version == 6: + return netaddr.IPAddress(self.prefix).format(netaddr.ipv6_full) + @property def depth(self): return self._depth @@ -808,6 +818,11 @@ def __init__(self, *args, **kwargs): self._original_assigned_object_id = self.__dict__.get('assigned_object_id') self._original_assigned_object_type_id = self.__dict__.get('assigned_object_type_id') + @property + def ipv6_full(self): + if self.address and self.address.version == 6: + return netaddr.IPAddress(self.address).format(netaddr.ipv6_full) + def get_duplicates(self): return IPAddress.objects.filter( vrf=self.vrf, diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index 12243e9b64e..9e027a70ef1 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -115,11 +115,13 @@ def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH): # "Starts/ends with" matches are valid only on string values query_filter &= Q(type=FieldTypes.STRING) - elif lookup == LookupTypes.PARTIAL: + elif lookup in (LookupTypes.PARTIAL, LookupTypes.EXACT): try: - # If the value looks like an IP address, add an extra match for CIDR values + # If the value looks like an IP address, add an extra filters for CIDR/INET values address = str(netaddr.IPNetwork(value.strip()).cidr) - query_filter |= Q(type=FieldTypes.CIDR) & Q(value__net_contains_or_equals=address) + query_filter |= Q(type=FieldTypes.INET) & Q(value__net_host=address) + if lookup == LookupTypes.PARTIAL: + query_filter |= Q(type=FieldTypes.CIDR) & Q(value__net_contains_or_equals=address) except (AddrFormatError, ValueError): pass From fb9607d2fe36e5a22646d910916277c1e2c21487 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Jul 2025 10:12:21 -0400 Subject: [PATCH 2/2] Fix typo --- netbox/netbox/search/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index 9e027a70ef1..cb08ab4afb8 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -117,7 +117,7 @@ def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE query_filter &= Q(type=FieldTypes.STRING) elif lookup in (LookupTypes.PARTIAL, LookupTypes.EXACT): try: - # If the value looks like an IP address, add an extra filters for CIDR/INET values + # If the value looks like an IP address, add extra filters for CIDR/INET values address = str(netaddr.IPNetwork(value.strip()).cidr) query_filter |= Q(type=FieldTypes.INET) & Q(value__net_host=address) if lookup == LookupTypes.PARTIAL: