瀏覽代碼

Fixes #23632: Treat null as a literal string in non-exact char filter lookups

Arthur 19 小時之前
父節點
當前提交
4302db4829
共有 2 個文件被更改,包括 30 次插入 和 0 次删除
  1. 6 0
      netbox/utilities/filters.py
  2. 24 0
      netbox/utilities/tests/test_filters.py

+ 6 - 0
netbox/utilities/filters.py

@@ -68,6 +68,12 @@ def multivalue_field_factory(field_class):
 class MultiValueCharFilter(django_filters.MultipleChoiceFilter):
     field_class = multivalue_field_factory(forms.CharField)
 
+    def get_filter_predicate(self, v):
+        # Only exact lookups accept None (IS NULL); treat the null value as a literal string otherwise
+        if v is None and self.lookup_expr not in ('exact', 'iexact'):
+            v = self.null_value
+        return super().get_filter_predicate(v)
+
 
 @extend_schema_field(OpenApiTypes.DATE)
 class MultiValueDateFilter(django_filters.MultipleChoiceFilter):

+ 24 - 0
netbox/utilities/tests/test_filters.py

@@ -715,6 +715,30 @@ class DynamicFilterLookupExpressionTestCase(TestCase):
         params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
         self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
 
+    def test_site_null_string_partial_lookups(self):
+        """
+        The null choice value must be treated as a literal string for lookups which cannot take None (#23632).
+        """
+        Site.objects.filter(name='Site 1').update(description='Null Site')
+        lookups = {
+            'description__ic': 1,
+            'description__nic': 2,
+            'description__isw': 1,
+            'description__nisw': 2,
+            'description__iew': 0,
+            'description__niew': 3,
+            'description__regex': 0,
+            'description__iregex': 1,
+        }
+        for lookup, count in lookups.items():
+            with self.subTest(lookup=lookup):
+                params = {lookup: ['null']}
+                self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), count)
+
+        # Exact lookups still translate the null choice value to IS NULL
+        self.assertEqual(SiteFilterSet({'name': ['null']}, Site.objects.all()).qs.count(), 0)
+        self.assertEqual(SiteFilterSet({'name__n': ['null']}, Site.objects.all()).qs.count(), 3)
+
     def test_provider_asn_lt(self):
         params = {'asn__lt': [65101]}
         self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)