Skip to content

Commit 47320f9

Browse files
authored
Merge pull request #19912 from miaow2/19903-regexp
Closes #19903: Add `regex` and `iregex` filter lookup expressions and corresponding tests
2 parents d08a1bd + c40bfb1 commit 47320f9

File tree

3 files changed

+48
-12
lines changed

3 files changed

+48
-12
lines changed

docs/reference/filtering.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,20 @@ GET /api/ipam/vlans/?vid__gt=900
8080

8181
String based (char) fields (Name, Address, etc) support these lookup expressions:
8282

83-
| Filter | Description |
84-
|---------|----------------------------------------|
85-
| `n` | Not equal to |
86-
| `ic` | Contains (case-insensitive) |
87-
| `nic` | Does not contain (case-insensitive) |
88-
| `isw` | Starts with (case-insensitive) |
89-
| `nisw` | Does not start with (case-insensitive) |
90-
| `iew` | Ends with (case-insensitive) |
91-
| `niew` | Does not end with (case-insensitive) |
92-
| `ie` | Exact match (case-insensitive) |
93-
| `nie` | Inverse exact match (case-insensitive) |
94-
| `empty` | Is empty/null (boolean) |
83+
| Filter | Description |
84+
|----------|----------------------------------------|
85+
| `n` | Not equal to |
86+
| `ic` | Contains (case-insensitive) |
87+
| `nic` | Does not contain (case-insensitive) |
88+
| `isw` | Starts with (case-insensitive) |
89+
| `nisw` | Does not start with (case-insensitive) |
90+
| `iew` | Ends with (case-insensitive) |
91+
| `niew` | Does not end with (case-insensitive) |
92+
| `ie` | Exact match (case-insensitive) |
93+
| `nie` | Inverse exact match (case-insensitive) |
94+
| `empty` | Is empty/null (boolean) |
95+
| `regex` | Regexp matching |
96+
| `iregex` | Regexp matching (case-insensitive) |
9597

9698
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:
9799

netbox/utilities/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
ie='iexact',
1414
nie='iexact',
1515
empty='empty',
16+
regex='regex',
17+
iregex='iregex',
1618
)
1719

1820
FILTER_NUMERIC_BASED_LOOKUP_MAP = dict(

netbox/utilities/tests/test_filters.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ def test_char_filter(self):
180180
self.assertEqual(self.filters['charfield__niew'].exclude, True)
181181
self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty')
182182
self.assertEqual(self.filters['charfield__empty'].exclude, False)
183+
self.assertEqual(self.filters['charfield__regex'].lookup_expr, 'regex')
184+
self.assertEqual(self.filters['charfield__regex'].exclude, False)
185+
self.assertEqual(self.filters['charfield__iregex'].lookup_expr, 'iregex')
186+
self.assertEqual(self.filters['charfield__iregex'].exclude, False)
183187

184188
def test_number_filter(self):
185189
self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter)
@@ -220,6 +224,10 @@ def test_mac_address_filter(self):
220224
self.assertEqual(self.filters['macaddressfield__iew'].exclude, False)
221225
self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith')
222226
self.assertEqual(self.filters['macaddressfield__niew'].exclude, True)
227+
self.assertEqual(self.filters['macaddressfield__regex'].lookup_expr, 'regex')
228+
self.assertEqual(self.filters['macaddressfield__regex'].exclude, False)
229+
self.assertEqual(self.filters['macaddressfield__iregex'].lookup_expr, 'iregex')
230+
self.assertEqual(self.filters['macaddressfield__iregex'].exclude, False)
223231

224232
def test_model_choice_filter(self):
225233
self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter)
@@ -257,6 +265,10 @@ def test_multi_value_char_filter(self):
257265
self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False)
258266
self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith')
259267
self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True)
268+
self.assertEqual(self.filters['multivaluecharfield__regex'].lookup_expr, 'regex')
269+
self.assertEqual(self.filters['multivaluecharfield__regex'].exclude, False)
270+
self.assertEqual(self.filters['multivaluecharfield__iregex'].lookup_expr, 'iregex')
271+
self.assertEqual(self.filters['multivaluecharfield__iregex'].exclude, False)
260272

261273
def test_multi_value_date_filter(self):
262274
self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter)
@@ -340,6 +352,10 @@ def test_multiple_choice_filter(self):
340352
self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False)
341353
self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith')
342354
self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True)
355+
self.assertEqual(self.filters['multiplechoicefield__regex'].lookup_expr, 'regex')
356+
self.assertEqual(self.filters['multiplechoicefield__regex'].exclude, False)
357+
self.assertEqual(self.filters['multiplechoicefield__iregex'].lookup_expr, 'iregex')
358+
self.assertEqual(self.filters['multiplechoicefield__iregex'].exclude, False)
343359

344360
def test_tag_filter(self):
345361
self.assertIsInstance(self.filters['tagfield'], TagFilter)
@@ -534,6 +550,14 @@ def test_site_slug_endswith_negation(self):
534550
params = {'slug__niew': ['-1']}
535551
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)
536552

553+
def test_site_slug_regex(self):
554+
params = {'slug__regex': ['^def-[a-z]*-2$']}
555+
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
556+
557+
def test_site_slug_iregex(self):
558+
params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
559+
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
560+
537561
def test_provider_asn_lt(self):
538562
params = {'asn__lt': [65101]}
539563
self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)
@@ -618,6 +642,14 @@ def test_device_mac_address_icontains_negation(self):
618642
params = {'mac_address__nic': ['aa:', 'bb']}
619643
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
620644

645+
def test_device_mac_address_regex(self):
646+
params = {'mac_address__regex': ['^cc.*:03$']}
647+
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
648+
649+
def test_device_mac_address_iregex(self):
650+
params = {'mac_address__iregex': ['^CC.*:03$']}
651+
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
652+
621653
def test_interface_rf_role_empty(self):
622654
params = {'rf_role__empty': 'true'}
623655
self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)

0 commit comments

Comments
 (0)