Przeglądaj źródła

Fixes #7093: Multi-select custom field filters should employ exact match

jeremystretch 4 lat temu
rodzic
commit
a8cdb3895b

+ 1 - 0
docs/release-notes/version-3.0.md

@@ -12,6 +12,7 @@
 * [#7083](https://github.com/netbox-community/netbox/issues/7083) - Correct labeling for VM memory attribute
 * [#7084](https://github.com/netbox-community/netbox/issues/7084) - Fix KeyError exception when editing access VLAN on an interface
 * [#7089](https://github.com/netbox-community/netbox/issues/7089) - Fix ContentTypeFilterSet not filtering on q filter
+* [#7093](https://github.com/netbox-community/netbox/issues/7093) - Multi-select custom field filters should employ exact match
 * [#7096](https://github.com/netbox-community/netbox/issues/7096) - Home links should honor `BASE_PATH` configuration
 * [#7101](https://github.com/netbox-community/netbox/issues/7101) - Enforce `MAX_PAGE_SIZE` for table and REST API pagination
 

+ 4 - 1
netbox/extras/filters.py

@@ -14,6 +14,7 @@ EXACT_FILTER_TYPES = (
     CustomFieldTypeChoices.TYPE_DATE,
     CustomFieldTypeChoices.TYPE_INTEGER,
     CustomFieldTypeChoices.TYPE_SELECT,
+    CustomFieldTypeChoices.TYPE_MULTISELECT,
 )
 
 
@@ -35,7 +36,9 @@ class CustomFieldFilter(django_filters.Filter):
 
         self.field_name = f'custom_field_data__{self.field_name}'
 
-        if custom_field.type not in EXACT_FILTER_TYPES:
+        if custom_field.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
+            self.lookup_expr = 'has_key'
+        elif custom_field.type not in EXACT_FILTER_TYPES:
             if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE:
                 self.lookup_expr = 'icontains'
 

+ 16 - 3
netbox/extras/tests/test_customfields.py

@@ -681,7 +681,12 @@ class CustomFieldFilterTest(TestCase):
         cf.content_types.set([obj_type])
 
         # Selection filtering
-        cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_URL, choices=['Foo', 'Bar', 'Baz'])
+        cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_SELECT, choices=['Foo', 'Bar', 'Baz'])
+        cf.save()
+        cf.content_types.set([obj_type])
+
+        # Multiselect filtering
+        cf = CustomField(name='cf9', type=CustomFieldTypeChoices.TYPE_MULTISELECT, choices=['A', 'AA', 'B', 'C'])
         cf.save()
         cf.content_types.set([obj_type])
 
@@ -695,6 +700,7 @@ class CustomFieldFilterTest(TestCase):
                 'cf6': 'http://foo.example.com/',
                 'cf7': 'http://foo.example.com/',
                 'cf8': 'Foo',
+                'cf9': ['A', 'B'],
             }),
             Site(name='Site 2', slug='site-2', custom_field_data={
                 'cf1': 200,
@@ -705,9 +711,9 @@ class CustomFieldFilterTest(TestCase):
                 'cf6': 'http://bar.example.com/',
                 'cf7': 'http://bar.example.com/',
                 'cf8': 'Bar',
+                'cf9': ['AA', 'B'],
             }),
-            Site(name='Site 3', slug='site-3', custom_field_data={
-            }),
+            Site(name='Site 3', slug='site-3'),
         ])
 
     def test_filter_integer(self):
@@ -730,3 +736,10 @@ class CustomFieldFilterTest(TestCase):
 
     def test_filter_select(self):
         self.assertEqual(self.filterset({'cf_cf8': 'Foo'}, self.queryset).qs.count(), 1)
+        self.assertEqual(self.filterset({'cf_cf8': 'Bar'}, self.queryset).qs.count(), 1)
+        self.assertEqual(self.filterset({'cf_cf8': 'Baz'}, self.queryset).qs.count(), 0)
+
+    def test_filter_multiselect(self):
+        self.assertEqual(self.filterset({'cf_cf9': 'A'}, self.queryset).qs.count(), 1)
+        self.assertEqual(self.filterset({'cf_cf9': 'B'}, self.queryset).qs.count(), 2)
+        self.assertEqual(self.filterset({'cf_cf9': 'C'}, self.queryset).qs.count(), 0)