mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Filter muiltiple ipaddress terms
This commit is contained in:
parent
6bc8f2e50b
commit
2e9f21e222
@ -11,6 +11,7 @@
|
|||||||
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
|
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
|
||||||
* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view
|
* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view
|
||||||
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
|
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
|
||||||
|
* [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable ipaddress filtering with multiple address terms
|
||||||
* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation
|
* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation
|
||||||
* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address
|
* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address
|
||||||
* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
|
* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from netaddr import AddrFormatError, IPNetwork
|
from netaddr import AddrFormatError, IPNetwork, IPAddress
|
||||||
|
|
||||||
from . import lookups
|
from . import lookups
|
||||||
from .formfields import IPFormField
|
from .formfields import IPFormField
|
||||||
@ -23,7 +23,10 @@ class BaseIPField(models.Field):
|
|||||||
if not value:
|
if not value:
|
||||||
return value
|
return value
|
||||||
try:
|
try:
|
||||||
return IPNetwork(value)
|
if '/' in str(value):
|
||||||
|
return IPNetwork(value)
|
||||||
|
else:
|
||||||
|
return IPAddress(value)
|
||||||
except AddrFormatError as e:
|
except AddrFormatError as e:
|
||||||
raise ValidationError("Invalid IP address format: {}".format(value))
|
raise ValidationError("Invalid IP address format: {}".format(value))
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
@ -32,6 +35,8 @@ class BaseIPField(models.Field):
|
|||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [str(self.to_python(v)) for v in value]
|
||||||
return str(self.to_python(value))
|
return str(self.to_python(value))
|
||||||
|
|
||||||
def form_class(self):
|
def form_class(self):
|
||||||
@ -90,5 +95,6 @@ IPAddressField.register_lookup(lookups.NetContainedOrEqual)
|
|||||||
IPAddressField.register_lookup(lookups.NetContains)
|
IPAddressField.register_lookup(lookups.NetContains)
|
||||||
IPAddressField.register_lookup(lookups.NetContainsOrEquals)
|
IPAddressField.register_lookup(lookups.NetContainsOrEquals)
|
||||||
IPAddressField.register_lookup(lookups.NetHost)
|
IPAddressField.register_lookup(lookups.NetHost)
|
||||||
|
IPAddressField.register_lookup(lookups.NetHostIn)
|
||||||
IPAddressField.register_lookup(lookups.NetHostContained)
|
IPAddressField.register_lookup(lookups.NetHostContained)
|
||||||
IPAddressField.register_lookup(lookups.NetMaskLength)
|
IPAddressField.register_lookup(lookups.NetMaskLength)
|
||||||
|
@ -7,7 +7,7 @@ from netaddr.core import AddrFormatError
|
|||||||
from dcim.models import Device, Interface, Region, Site
|
from dcim.models import Device, Interface, Region, Site
|
||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, MultiValueCharFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
@ -284,7 +284,7 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
method='search_by_parent',
|
method='search_by_parent',
|
||||||
label='Parent prefix',
|
label='Parent prefix',
|
||||||
)
|
)
|
||||||
address = django_filters.CharFilter(
|
address = MultiValueCharFilter(
|
||||||
method='filter_address',
|
method='filter_address',
|
||||||
label='Address',
|
label='Address',
|
||||||
)
|
)
|
||||||
@ -371,13 +371,11 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
def filter_address(self, queryset, name, value):
|
def filter_address(self, queryset, name, value):
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
try:
|
try:
|
||||||
# Match address and subnet mask
|
return queryset.filter(
|
||||||
if '/' in value:
|
Q(address__in=value) |
|
||||||
return queryset.filter(address=value)
|
Q(address__net_host_in=value)
|
||||||
return queryset.filter(address__net_host=value)
|
)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
@ -100,6 +100,25 @@ class NetHost(Lookup):
|
|||||||
return 'HOST(%s) = %s' % (lhs, rhs), params
|
return 'HOST(%s) = %s' % (lhs, rhs), params
|
||||||
|
|
||||||
|
|
||||||
|
class NetHostIn(Lookup):
|
||||||
|
lookup_name = 'net_host_in'
|
||||||
|
|
||||||
|
def as_sql(self, qn, connection):
|
||||||
|
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||||
|
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||||
|
in_elements = ['HOST(%s) IN (' % lhs]
|
||||||
|
params = []
|
||||||
|
for offset in range(0, len(rhs_params[0])):
|
||||||
|
if offset > 0:
|
||||||
|
in_elements.append(', ')
|
||||||
|
params.extend(lhs_params)
|
||||||
|
sqls_params = rhs_params[0][offset]
|
||||||
|
in_elements.append(rhs)
|
||||||
|
params.append(sqls_params)
|
||||||
|
in_elements.append(')')
|
||||||
|
return ''.join(in_elements), params
|
||||||
|
|
||||||
|
|
||||||
class NetHostContained(Lookup):
|
class NetHostContained(Lookup):
|
||||||
"""
|
"""
|
||||||
Check for the host portion of an IP address without regard to its mask. This allows us to find e.g. 192.0.2.1/24
|
Check for the host portion of an IP address without regard to its mask. This allows us to find e.g. 192.0.2.1/24
|
||||||
|
@ -337,16 +337,18 @@ class IPAddressTestCase(TestCase):
|
|||||||
IPAddress(family=4, address='10.0.0.2/24', vrf=vrfs[0], interface=interfaces[0], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
|
IPAddress(family=4, address='10.0.0.2/24', vrf=vrfs[0], interface=interfaces[0], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
|
||||||
IPAddress(family=4, address='10.0.0.3/24', vrf=vrfs[1], interface=interfaces[1], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
|
IPAddress(family=4, address='10.0.0.3/24', vrf=vrfs[1], interface=interfaces[1], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
|
||||||
IPAddress(family=4, address='10.0.0.4/24', vrf=vrfs[2], interface=interfaces[2], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
|
IPAddress(family=4, address='10.0.0.4/24', vrf=vrfs[2], interface=interfaces[2], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
|
||||||
|
IPAddress(family=4, address='10.0.0.1/25', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None),
|
||||||
IPAddress(family=6, address='2001:db8::1/64', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'),
|
IPAddress(family=6, address='2001:db8::1/64', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'),
|
||||||
IPAddress(family=6, address='2001:db8::2/64', vrf=vrfs[0], interface=interfaces[3], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
|
IPAddress(family=6, address='2001:db8::2/64', vrf=vrfs[0], interface=interfaces[3], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
|
||||||
IPAddress(family=6, address='2001:db8::3/64', vrf=vrfs[1], interface=interfaces[4], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
|
IPAddress(family=6, address='2001:db8::3/64', vrf=vrfs[1], interface=interfaces[4], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
|
||||||
IPAddress(family=6, address='2001:db8::4/64', vrf=vrfs[2], interface=interfaces[5], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
|
IPAddress(family=6, address='2001:db8::4/64', vrf=vrfs[2], interface=interfaces[5], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
|
||||||
|
IPAddress(family=6, address='2001:db8::1/65', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None),
|
||||||
)
|
)
|
||||||
IPAddress.objects.bulk_create(ipaddresses)
|
IPAddress.objects.bulk_create(ipaddresses)
|
||||||
|
|
||||||
def test_family(self):
|
def test_family(self):
|
||||||
params = {'family': '6'}
|
params = {'family': '6'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
|
|
||||||
def test_dns_name(self):
|
def test_dns_name(self):
|
||||||
params = {'dns_name': ['ipaddress-a', 'ipaddress-b']}
|
params = {'dns_name': ['ipaddress-a', 'ipaddress-b']}
|
||||||
@ -359,20 +361,24 @@ class IPAddressTestCase(TestCase):
|
|||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
params = {'parent': '10.0.0.0/24'}
|
params = {'parent': '10.0.0.0/24'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
params = {'parent': '2001:db8::/64'}
|
params = {'parent': '2001:db8::/64'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
|
|
||||||
def filter_address(self):
|
def test_filter_address(self):
|
||||||
# Check IPv4 and IPv6, with and without a mask
|
# Check IPv4 and IPv6, with and without a mask
|
||||||
params = {'address': '10.0.0.1/24'}
|
params = {'address': ['10.0.0.1/24']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
params = {'address': '10.0.0.1'}
|
params = {'address': ['10.0.0.1']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'address': '2001:db8::1/64'}
|
params = {'address': ['10.0.0.1/24', '10.0.0.1/25']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'address': '2001:db8::1'}
|
params = {'address': ['2001:db8::1/64']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'address': ['2001:db8::1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'address': ['2001:db8::1/64', '2001:db8::1/65']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_mask_length(self):
|
def test_mask_length(self):
|
||||||
params = {'mask_length': '24'}
|
params = {'mask_length': '24'}
|
||||||
@ -411,7 +417,7 @@ class IPAddressTestCase(TestCase):
|
|||||||
params = {'assigned_to_interface': 'true'}
|
params = {'assigned_to_interface': 'true'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'assigned_to_interface': 'false'}
|
params = {'assigned_to_interface': 'false'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_status(self):
|
def test_status(self):
|
||||||
params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]}
|
params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]}
|
||||||
|
Loading…
Reference in New Issue
Block a user