diff --git a/docs/reference/filtering.md b/docs/reference/filtering.md index 7ddda6f3c..5a672ed11 100644 --- a/docs/reference/filtering.md +++ b/docs/reference/filtering.md @@ -61,13 +61,14 @@ These lookup expressions can be applied by adding a suffix to the desired field' Numeric based fields (ASN, VLAN ID, etc) support these lookup expressions: -| Filter | Description | -|--------|-------------| -| `n` | Not equal to | -| `lt` | Less than | -| `lte` | Less than or equal to | -| `gt` | Greater than | -| `gte` | Greater than or equal to | +| Filter | Description | +|---------|--------------------------| +| `n` | Not equal to | +| `lt` | Less than | +| `lte` | Less than or equal to | +| `gt` | Greater than | +| `gte` | Greater than or equal to | +| `empty` | Is empty/null (boolean) | Here is an example of a numeric field lookup expression that will return all VLANs with a VLAN ID greater than 900: @@ -79,18 +80,18 @@ GET /api/ipam/vlans/?vid__gt=900 String based (char) fields (Name, Address, etc) support these lookup expressions: -| Filter | Description | -|--------|-------------| -| `n` | Not equal to | -| `ic` | Contains (case-insensitive) | -| `nic` | Does not contain (case-insensitive) | -| `isw` | Starts with (case-insensitive) | -| `nisw` | Does not start with (case-insensitive) | -| `iew` | Ends with (case-insensitive) | -| `niew` | Does not end with (case-insensitive) | -| `ie` | Exact match (case-insensitive) | -| `nie` | Inverse exact match (case-insensitive) | -| `empty` | Is empty (boolean) | +| Filter | Description | +|---------|----------------------------------------| +| `n` | Not equal to | +| `ic` | Contains (case-insensitive) | +| `nic` | Does not contain (case-insensitive) | +| `isw` | Starts with (case-insensitive) | +| `nisw` | Does not start with (case-insensitive) | +| `iew` | Ends with (case-insensitive) | +| `niew` | Does not end with (case-insensitive) | +| `ie` | Exact match (case-insensitive) | +| `nie` | Inverse exact match (case-insensitive) | +| `empty` | Is empty/null (boolean) | Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name: diff --git a/netbox/utilities/constants.py b/netbox/utilities/constants.py index 975a2f48d..5c551a810 100644 --- a/netbox/utilities/constants.py +++ b/netbox/utilities/constants.py @@ -20,7 +20,8 @@ FILTER_NUMERIC_BASED_LOOKUP_MAP = dict( lte='lte', lt='lt', gte='gte', - gt='gt' + gt='gt', + empty='isnull', ) FILTER_NEGATION_LOOKUP_MAP = dict( diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 334f270dc..16fd3d92e 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -86,6 +86,10 @@ class DummyModel(models.Model): charfield = models.CharField( max_length=10 ) + numberfield = models.IntegerField( + blank=True, + null=True + ) choicefield = models.IntegerField( choices=(('A', 1), ('B', 2), ('C', 3)) ) @@ -108,6 +112,7 @@ class BaseFilterSetTest(TestCase): """ class DummyFilterSet(BaseFilterSet): charfield = django_filters.CharFilter() + numberfield = django_filters.NumberFilter() macaddressfield = MACAddressFilter() modelchoicefield = django_filters.ModelChoiceFilter( field_name='integerfield', # We're pretending this is a ForeignKey field @@ -132,6 +137,7 @@ class BaseFilterSetTest(TestCase): model = DummyModel fields = ( 'charfield', + 'numberfield', 'choicefield', 'datefield', 'datetimefield', @@ -171,6 +177,25 @@ class BaseFilterSetTest(TestCase): self.assertEqual(self.filters['charfield__iew'].exclude, False) self.assertEqual(self.filters['charfield__niew'].lookup_expr, 'iendswith') self.assertEqual(self.filters['charfield__niew'].exclude, True) + self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty') + self.assertEqual(self.filters['charfield__empty'].exclude, False) + + def test_number_filter(self): + self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter) + self.assertEqual(self.filters['numberfield'].lookup_expr, 'exact') + self.assertEqual(self.filters['numberfield'].exclude, False) + self.assertEqual(self.filters['numberfield__n'].lookup_expr, 'exact') + self.assertEqual(self.filters['numberfield__n'].exclude, True) + self.assertEqual(self.filters['numberfield__lt'].lookup_expr, 'lt') + self.assertEqual(self.filters['numberfield__lt'].exclude, False) + self.assertEqual(self.filters['numberfield__lte'].lookup_expr, 'lte') + self.assertEqual(self.filters['numberfield__lte'].exclude, False) + self.assertEqual(self.filters['numberfield__gt'].lookup_expr, 'gt') + self.assertEqual(self.filters['numberfield__gt'].exclude, False) + self.assertEqual(self.filters['numberfield__gte'].lookup_expr, 'gte') + self.assertEqual(self.filters['numberfield__gte'].exclude, False) + self.assertEqual(self.filters['numberfield__empty'].lookup_expr, 'isnull') + self.assertEqual(self.filters['numberfield__empty'].exclude, False) def test_mac_address_filter(self): self.assertIsInstance(self.filters['macaddressfield'], MACAddressFilter)