lookup --> query
This commit is contained in:
@@ -36,8 +36,8 @@ ID_KWARGS = ["in", "exact"]
|
||||
INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"]
|
||||
DATE_KWARGS = ["year", "month", "day", "date__gt", "gt", "date__lt", "lt"]
|
||||
|
||||
CUSTOM_FIELD_LOOKUP_MAX_DEPTH = 10
|
||||
CUSTOM_FIELD_LOOKUP_MAX_ATOMS = 20
|
||||
CUSTOM_FIELD_QUERY_MAX_DEPTH = 10
|
||||
CUSTOM_FIELD_QUERY_MAX_ATOMS = 20
|
||||
|
||||
|
||||
class CorrespondentFilterSet(FilterSet):
|
||||
@@ -237,7 +237,7 @@ def handle_validation_prefix(func: Callable):
|
||||
return wrapper
|
||||
|
||||
|
||||
class CustomFieldLookupParser:
|
||||
class CustomFieldQueryParser:
|
||||
EXPR_BY_CATEGORY = {
|
||||
"basic": ["exact", "in", "isnull", "exists"],
|
||||
"string": [
|
||||
@@ -351,7 +351,7 @@ class CustomFieldLookupParser:
|
||||
elif len(expr) == 3:
|
||||
return self._parse_atom(*expr)
|
||||
raise serializers.ValidationError(
|
||||
[_("Invalid custom field lookup expression")],
|
||||
[_("Invalid custom field query expression")],
|
||||
)
|
||||
|
||||
@handle_validation_prefix
|
||||
@@ -485,7 +485,7 @@ class CustomFieldLookupParser:
|
||||
if not supported:
|
||||
raise serializers.ValidationError(
|
||||
[
|
||||
_("{data_type} does not support lookup expr {expr!r}.").format(
|
||||
_("{data_type} does not support query expr {expr!r}.").format(
|
||||
data_type=custom_field.data_type,
|
||||
expr=raw_op,
|
||||
),
|
||||
@@ -506,7 +506,7 @@ class CustomFieldLookupParser:
|
||||
custom_field.data_type == CustomField.FieldDataType.DATE
|
||||
and prefix in self.DATE_COMPONENTS
|
||||
):
|
||||
# DateField admits lookups in the form of `year__exact`, etc. These take integers.
|
||||
# DateField admits queries in the form of `year__exact`, etc. These take integers.
|
||||
field = serializers.IntegerField()
|
||||
elif custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
|
||||
# We can be more specific here and make sure the value is a list.
|
||||
@@ -568,7 +568,7 @@ class CustomFieldLookupParser:
|
||||
custom_fields__value_document_ids__isnull=False,
|
||||
)
|
||||
|
||||
# First we lookup reverse links from the requested documents.
|
||||
# First we look up reverse links from the requested documents.
|
||||
links = CustomFieldInstance.objects.filter(
|
||||
document_id__in=value,
|
||||
field__data_type=CustomField.FieldDataType.DOCUMENTLINK,
|
||||
@@ -600,7 +600,7 @@ class CustomFieldLookupParser:
|
||||
self._current_depth -= 1
|
||||
|
||||
|
||||
class CustomFieldLookupFilter(Filter):
|
||||
class CustomFieldQueryFilter(Filter):
|
||||
def __init__(self, validation_prefix):
|
||||
"""
|
||||
A filter that filters documents based on custom field name and value.
|
||||
@@ -615,10 +615,10 @@ class CustomFieldLookupFilter(Filter):
|
||||
if not value:
|
||||
return qs
|
||||
|
||||
parser = CustomFieldLookupParser(
|
||||
parser = CustomFieldQueryParser(
|
||||
self._validation_prefix,
|
||||
max_query_depth=CUSTOM_FIELD_LOOKUP_MAX_DEPTH,
|
||||
max_atom_count=CUSTOM_FIELD_LOOKUP_MAX_ATOMS,
|
||||
max_query_depth=CUSTOM_FIELD_QUERY_MAX_DEPTH,
|
||||
max_atom_count=CUSTOM_FIELD_QUERY_MAX_ATOMS,
|
||||
)
|
||||
q, annotations = parser.parse(value)
|
||||
|
||||
@@ -672,7 +672,7 @@ class DocumentFilterSet(FilterSet):
|
||||
exclude=True,
|
||||
)
|
||||
|
||||
custom_field_lookup = CustomFieldLookupFilter("custom_field_lookup")
|
||||
custom_field_query = CustomFieldQueryFilter("custom_field_query")
|
||||
|
||||
shared_by__id = SharedByUser()
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ class SavedViewFilterRule(models.Model):
|
||||
(39, _("has custom field in")),
|
||||
(40, _("does not have custom field in")),
|
||||
(41, _("does not have custom field")),
|
||||
(42, _("custom fields lookup")),
|
||||
(42, _("custom fields query")),
|
||||
]
|
||||
|
||||
saved_view = models.ForeignKey(
|
||||
|
||||
@@ -181,7 +181,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
"/api/documents/?"
|
||||
+ "&".join(
|
||||
(
|
||||
f"custom_field_lookup={query_string}",
|
||||
f"custom_field_query={query_string}",
|
||||
"ordering=archive_serial_number",
|
||||
"page=1",
|
||||
f"page_size={len(self.documents)}",
|
||||
@@ -205,7 +205,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
"/api/documents/?"
|
||||
+ "&".join(
|
||||
(
|
||||
f"custom_field_lookup={query_string}",
|
||||
f"custom_field_query={query_string}",
|
||||
"ordering=archive_serial_number",
|
||||
"page=1",
|
||||
f"page_size={len(self.documents)}",
|
||||
@@ -477,57 +477,57 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
def test_invalid_json(self):
|
||||
self._assert_validation_error(
|
||||
"not valid json",
|
||||
["custom_field_lookup"],
|
||||
["custom_field_query"],
|
||||
"must be valid JSON",
|
||||
)
|
||||
|
||||
def test_invalid_expression(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps("valid json but not valid expr"),
|
||||
["custom_field_lookup"],
|
||||
"Invalid custom field lookup expression",
|
||||
["custom_field_query"],
|
||||
"Invalid custom field query expression",
|
||||
)
|
||||
|
||||
def test_invalid_custom_field_name(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["invalid name", "iexact", "foo"]),
|
||||
["custom_field_lookup", "0"],
|
||||
["custom_field_query", "0"],
|
||||
"is not a valid custom field",
|
||||
)
|
||||
|
||||
def test_invalid_operator(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["integer_field", "iexact", "foo"]),
|
||||
["custom_field_lookup", "1"],
|
||||
"does not support lookup expr",
|
||||
["custom_field_query", "1"],
|
||||
"does not support query expr",
|
||||
)
|
||||
|
||||
def test_invalid_value(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["select_field", "exact", "not an option"]),
|
||||
["custom_field_lookup", "2"],
|
||||
["custom_field_query", "2"],
|
||||
"integer",
|
||||
)
|
||||
|
||||
def test_invalid_logical_operator(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["invalid op", ["integer_field", "gt", 0]]),
|
||||
["custom_field_lookup", "0"],
|
||||
["custom_field_query", "0"],
|
||||
"Invalid logical operator",
|
||||
)
|
||||
|
||||
def test_invalid_expr_list(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["AND", "not a list"]),
|
||||
["custom_field_lookup", "1"],
|
||||
["custom_field_query", "1"],
|
||||
"Invalid expression list",
|
||||
)
|
||||
|
||||
def test_invalid_operator_prefix(self):
|
||||
self._assert_validation_error(
|
||||
json.dumps(["integer_field", "foo__gt", 0]),
|
||||
["custom_field_lookup", "1"],
|
||||
"does not support lookup expr",
|
||||
["custom_field_query", "1"],
|
||||
"does not support query expr",
|
||||
)
|
||||
|
||||
def test_query_too_deep(self):
|
||||
@@ -536,7 +536,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
query = ["NOT", query]
|
||||
self._assert_validation_error(
|
||||
json.dumps(query),
|
||||
["custom_field_lookup", *(["1"] * 10)],
|
||||
["custom_field_query", *(["1"] * 10)],
|
||||
"Maximum nesting depth exceeded",
|
||||
)
|
||||
|
||||
@@ -545,6 +545,6 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
query = ["AND", [atom for _ in range(21)]]
|
||||
self._assert_validation_error(
|
||||
json.dumps(query),
|
||||
["custom_field_lookup", "1", "20"],
|
||||
["custom_field_query", "1", "20"],
|
||||
"Maximum number of query conditions exceeded",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user