Closes #18811: Match full-form IPv6 addresses in global search (#19873)

* Closes #18811: Match full-form IPv6 addresses in global search

* Fix typo
This commit is contained in:
Jeremy Stretch 2025-07-14 10:28:30 -04:00 committed by GitHub
parent b5421f1cd6
commit f05897d61a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 4 deletions

View File

@ -18,9 +18,22 @@ class Empty(Lookup):
return f"CAST(LENGTH({sql}) AS BOOLEAN) IS TRUE", params 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): 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' lookup_name = 'net_contains_or_equals'
@ -32,4 +45,5 @@ class NetContainsOrEquals(Lookup):
CharField.register_lookup(Empty) CharField.register_lookup(Empty)
CachedValueField.register_lookup(NetHost)
CachedValueField.register_lookup(NetContainsOrEquals) CachedValueField.register_lookup(NetContainsOrEquals)

View File

@ -162,6 +162,11 @@ class Aggregate(ContactsMixin, GetAvailablePrefixesMixin, PrimaryModel):
return self.prefix.version return self.prefix.version
return None 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): def get_child_prefixes(self):
""" """
Return all Prefixes within this Aggregate Return all Prefixes within this Aggregate
@ -330,6 +335,11 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
def mask_length(self): def mask_length(self):
return self.prefix.prefixlen if self.prefix else None 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 @property
def depth(self): def depth(self):
return self._depth return self._depth
@ -808,6 +818,11 @@ class IPAddress(ContactsMixin, PrimaryModel):
self._original_assigned_object_id = self.__dict__.get('assigned_object_id') self._original_assigned_object_id = self.__dict__.get('assigned_object_id')
self._original_assigned_object_type_id = self.__dict__.get('assigned_object_type_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): def get_duplicates(self):
return IPAddress.objects.filter( return IPAddress.objects.filter(
vrf=self.vrf, vrf=self.vrf,

View File

@ -115,11 +115,13 @@ class CachedValueSearchBackend(SearchBackend):
if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH): if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH):
# "Starts/ends with" matches are valid only on string values # "Starts/ends with" matches are valid only on string values
query_filter &= Q(type=FieldTypes.STRING) query_filter &= Q(type=FieldTypes.STRING)
elif lookup == LookupTypes.PARTIAL: elif lookup in (LookupTypes.PARTIAL, LookupTypes.EXACT):
try: 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 extra filters for CIDR/INET values
address = str(netaddr.IPNetwork(value.strip()).cidr) 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): except (AddrFormatError, ValueError):
pass pass