]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: correctly handle "exists, false" in custom field query filter (#8158)
authorYichi Yang <yiy067@ucsd.edu>
Sun, 3 Nov 2024 01:40:50 +0000 (18:40 -0700)
committerGitHub <noreply@github.com>
Sun, 3 Nov 2024 01:40:50 +0000 (18:40 -0700)
src/documents/filters.py
src/documents/tests/test_api_filter_by_custom_fields.py

index f0a9a55b360493ef03d6eada58687bbf1f98f5c7..e8065c472a1c704cab51005aa78ddfca0131006e 100644 (file)
@@ -424,20 +424,28 @@ class CustomFieldQueryParser:
             value_field_name = "value_monetary_amount"
         has_field = Q(custom_fields__field=custom_field)
 
+        # We need to use an annotation here because different atoms
+        # might be referring to different instances of custom fields.
+        annotation_name = f"_custom_field_filter_{len(self._annotations)}"
+
         # Our special exists operator.
         if op == "exists":
-            field_filter = has_field if value else ~has_field
+            annotation = Count("custom_fields", filter=has_field)
+            # A Document should have > 0 match if it has this field, or 0 if doesn't.
+            query_op = "gt" if value else "exact"
+            query = Q(**{f"{annotation_name}__{query_op}": 0})
         else:
+            # Check if 1) custom field name matches, and 2) value satisfies condition
             field_filter = has_field & Q(
                 **{f"custom_fields__{value_field_name}__{op}": value},
             )
+            # Annotate how many matching custom fields each document has
+            annotation = Count("custom_fields", filter=field_filter)
+            # Filter document by count
+            query = Q(**{f"{annotation_name}__gt": 0})
 
-        # We need to use an annotation here because different atoms
-        # might be referring to different instances of custom fields.
-        annotation_name = f"_custom_field_filter_{len(self._annotations)}"
-        self._annotations[annotation_name] = Count("custom_fields", filter=field_filter)
-
-        return Q(**{f"{annotation_name}__gt": 0})
+        self._annotations[annotation_name] = annotation
+        return query
 
     @handle_validation_prefix
     def _get_custom_field(self, id_or_name):
index c9a0cdcfc4c68dba6d96cbc6f809e9cd127dc747..4cba291525d21b6c06892f526c2ab84497ca2122 100644 (file)
@@ -289,6 +289,12 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
             lambda document: "string_field" in document,
         )
 
+    def test_exists_false(self):
+        self._assert_query_match_predicate(
+            ["string_field", "exists", False],
+            lambda document: "string_field" not in document,
+        )
+
     def test_select(self):
         # For select fields, you can either specify the index
         # or the name of the option. They function exactly the same.