From 052359f6e5de34157ddb12e8aa172d0f436c310d Mon Sep 17 00:00:00 2001 From: Anna Shamray Date: Thu, 12 Dec 2024 15:33:54 +0100 Subject: [PATCH] :sparkles: [#472] add validation for 'data_attr' filter --- src/objects/api/v2/filters.py | 7 +++-- src/objects/api/v2/openapi.yaml | 6 ++-- src/objects/api/validators.py | 56 +++++++++++++++++++++++---------- src/objects/utils/filters.py | 1 - 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/objects/api/v2/filters.py b/src/objects/api/v2/filters.py index 8af36773..f962787c 100644 --- a/src/objects/api/v2/filters.py +++ b/src/objects/api/v2/filters.py @@ -13,7 +13,7 @@ from ..constants import Operators from ..utils import display_choice_values_for_help_text, string_to_value -from ..validators import validate_data_attrs +from ..validators import validate_data_attr, validate_data_attrs DATA_ATTR_VALUE_HELP_TEXT = f"""A valid parameter value has the form `key__operator__value`. `key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. @@ -102,7 +102,8 @@ class ObjectRecordFilterSet(FilterSet): method="filter_data_attrs", validators=[validate_data_attrs], help_text=_( - """**DEPRECATED**: Only include objects that have attributes with certain values. + """**DEPRECATED: Use 'data_attr' instead**. +Only include objects that have attributes with certain values. Data filtering expressions are comma-separated and are structured as follows: %(value_part_help_text)s @@ -120,7 +121,7 @@ class ObjectRecordFilterSet(FilterSet): data_attr = ManyCharFilter( method="filter_data_attr", - # validators=[validate_data_attrs], + validators=[validate_data_attr], help_text=_( """Only include objects that have attributes with certain values. diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index 3d84125b..13bd3b3b 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -129,7 +129,8 @@ paths: schema: type: string description: | - **DEPRECATED**: Only include objects that have attributes with certain values. + **DEPRECATED: Use 'data_attr' instead**. + Only include objects that have attributes with certain values. Data filtering expressions are comma-separated and are structured as follows: A valid parameter value has the form `key__operator__value`. @@ -662,7 +663,8 @@ paths: data_attrs: type: string description: | - **DEPRECATED**: Only include objects that have attributes with certain values. + **DEPRECATED: Use 'data_attr' instead**. + Only include objects that have attributes with certain values. Data filtering expressions are comma-separated and are structured as follows: A valid parameter value has the form `key__operator__value`. diff --git a/src/objects/api/validators.py b/src/objects/api/validators.py index 11b86567..68f25593 100644 --- a/src/objects/api/validators.py +++ b/src/objects/api/validators.py @@ -68,32 +68,54 @@ def __call__(self, new_value, serializer_field): raise serializers.ValidationError(self.message, code=self.code) +def validate_data_attr_value_part(value_part: str, code: str): + try: + variable, operator, val = value_part.rsplit("__", 2) + except ValueError: + message = _( + "Filter expression '%(value_part)s' doesn't have the shape 'key__operator__value'" + ) % {"value_part": value_part} + raise serializers.ValidationError(message, code=code) + + if operator not in Operators.values: + message = _("Comparison operator `%(operator)s` is unknown") % { + "operator": operator + } + raise serializers.ValidationError(message, code=code) + + if operator not in ( + Operators.exact, + Operators.icontains, + Operators.in_list, + ) and isinstance(string_to_value(val), str): + message = _( + "Operator `%(operator)s` supports only dates and/or numeric values" + ) % {"operator": operator} + raise serializers.ValidationError(message, code=code) + + def validate_data_attrs(value: str): + # todo remove when 'data_attrs' filter is removed code = "invalid-data-attrs-query" parts = value.split(",") for value_part in parts: - try: - variable, operator, val = value_part.rsplit("__", 2) - except ValueError as exc: - raise serializers.ValidationError(exc.args[0], code=code) from exc - - if operator not in Operators.values: - message = _("Comparison operator `%(operator)s` is unknown") % { - "operator": operator - } - raise serializers.ValidationError(message, code=code) + validate_data_attr_value_part(value_part, code) - if operator not in ( - Operators.exact, - Operators.icontains, - Operators.in_list, - ) and isinstance(string_to_value(val), str): + +def validate_data_attr(value: list): + code = "invalid-data-attr-query" + + for value_part in value: + # check that comma can be only in the value part + if "," in value_part.rsplit("__", 1)[0]: message = _( - "Operator `%(operator)s` supports only dates and/or numeric values" - ) % {"operator": operator} + "Filter expression '%(value_part)s' doesn't have the shape 'key__operator__value'" + ) % {"value_part": value_part} raise serializers.ValidationError(message, code=code) + validate_data_attr_value_part(value_part, code) + class GeometryValidator: code = "geometry-not-allowed" diff --git a/src/objects/utils/filters.py b/src/objects/utils/filters.py index 0f19187c..421c0a2c 100644 --- a/src/objects/utils/filters.py +++ b/src/objects/utils/filters.py @@ -66,7 +66,6 @@ def to_python(self, value): if not value: return [] - # todo validator if it's list return value