This commit is contained in:
Artem Kotik 2025-07-22 22:38:31 +03:00 committed by GitHub
commit 7b3a7caa7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 48 additions and 12 deletions

View File

@ -80,18 +80,20 @@ 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/null (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) |
| `regex` | Regexp matching |
| `iregex` | Regexp matching (case-insensitive) |
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:

View File

@ -13,6 +13,8 @@ FILTER_CHAR_BASED_LOOKUP_MAP = dict(
ie='iexact',
nie='iexact',
empty='empty',
regex='regex',
iregex='iregex',
)
FILTER_NUMERIC_BASED_LOOKUP_MAP = dict(

View File

@ -180,6 +180,10 @@ class BaseFilterSetTest(TestCase):
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)
self.assertEqual(self.filters['charfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['charfield__regex'].exclude, False)
self.assertEqual(self.filters['charfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['charfield__iregex'].exclude, False)
def test_number_filter(self):
self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter)
@ -220,6 +224,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['macaddressfield__iew'].exclude, False)
self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['macaddressfield__niew'].exclude, True)
self.assertEqual(self.filters['macaddressfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['macaddressfield__regex'].exclude, False)
self.assertEqual(self.filters['macaddressfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['macaddressfield__iregex'].exclude, False)
def test_model_choice_filter(self):
self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter)
@ -257,6 +265,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True)
self.assertEqual(self.filters['multivaluecharfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multivaluecharfield__regex'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multivaluecharfield__iregex'].exclude, False)
def test_multi_value_date_filter(self):
self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter)
@ -340,6 +352,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True)
self.assertEqual(self.filters['multiplechoicefield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multiplechoicefield__regex'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multiplechoicefield__iregex'].exclude, False)
def test_tag_filter(self):
self.assertIsInstance(self.filters['tagfield'], TagFilter)
@ -534,6 +550,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'slug__niew': ['-1']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)
def test_site_slug_regex(self):
params = {'slug__regex': ['^def-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_site_slug_iregex(self):
params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_provider_asn_lt(self):
params = {'asn__lt': [65101]}
self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)
@ -618,6 +642,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'mac_address__nic': ['aa:', 'bb']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_regex(self):
params = {'mac_address__regex': ['^cc.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_iregex(self):
params = {'mac_address__iregex': ['^CC.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_interface_rf_role_empty(self):
params = {'rf_role__empty': 'true'}
self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)