Repository: netbox-community/netbox

Issue:
API filtering 'OR' logic not working when filtering IP addresses by device ID
### Environment
* Python version:  3.6.8
* NetBox version:  2.6.3

### Steps to Reproduce
Issue an API query with filter parameters specifying two different device IDs, for example:
https://netbox/api/ipam/ip-addresses/?device_id=13441&device_id=13442

### Expected Behavior
API will return IP addresses associated with the devices having ids 13441 and 13442.

### Observed Behavior
API returned only the IP addresses associated with the last given device id, in this case 13442.


Discussion:
device_id in ipam/filters.py is a NumberFilter.

This could at least be upgraded to a ModelMultipleChoiceFilter.

Resolving patch:
diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py
--- a/netbox/ipam/filters.py
+++ b/netbox/ipam/filters.py
@@ -8,7 +8,7 @@
 from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
 from tenancy.filters import TenancyFilterSet
 from utilities.filters import (
-    MultiValueCharFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
 )
 from virtualization.models import VirtualMachine
 from .choices import *
@@ -304,12 +304,12 @@ class IPAddressFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedF
         to_field_name='rd',
         label='VRF (RD)',
     )
-    device = django_filters.CharFilter(
+    device = MultiValueCharFilter(
         method='filter_device',
         field_name='name',
-        label='Device',
+        label='Device (name)',
     )
-    device_id = django_filters.NumberFilter(
+    device_id = MultiValueNumberFilter(
         method='filter_device',
         field_name='pk',
         label='Device (ID)',
@@ -385,8 +385,10 @@ def filter_mask_length(self, queryset, name, value):
 
     def filter_device(self, queryset, name, value):
         try:
-            device = Device.objects.prefetch_related('device_type').get(**{name: value})
-            vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
+            devices = Device.objects.prefetch_related('device_type').filter(**{'{}__in'.format(name): value})
+            vc_interface_ids = []
+            for device in devices:
+                vc_interface_ids.extend([i['id'] for i in device.vc_interfaces.values('id')])
             return queryset.filter(interface_id__in=vc_interface_ids)
         except Device.DoesNotExist:
             return queryset.none()


Test patch:
diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py
--- a/netbox/ipam/tests/test_filters.py
+++ b/netbox/ipam/tests/test_filters.py
@@ -392,13 +392,12 @@ def test_vrf(self):
         params = {'vrf': [vrfs[0].rd, vrfs[1].rd]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
-    # TODO: Test for multiple values
     def test_device(self):
-        device = Device.objects.first()
-        params = {'device_id': device.pk}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
-        params = {'device': device.name}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+        devices = Device.objects.all()[:2]
+        params = {'device_id': [devices[0].pk, devices[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        params = {'device': [devices[0].name, devices[1].name]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
     def test_virtual_machine(self):
         vms = VirtualMachine.objects.all()[:2]