From 1522017ddfcfbf76c0cc7280e9727d6dbdd9e9c1 Mon Sep 17 00:00:00 2001 From: Conor Holden Date: Tue, 27 Aug 2024 17:18:13 +0200 Subject: [PATCH 1/3] :sparkles:[#414] add in operator to API --- src/objects/api/constants.py | 1 + src/objects/api/v1/filters.py | 4 ++++ src/objects/api/v2/filters.py | 4 ++++ src/objects/api/validators.py | 8 +++++--- src/objects/tests/v1/test_filters.py | 25 +++++++++++++++++++++++++ src/objects/tests/v2/test_filters.py | 27 +++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/objects/api/constants.py b/src/objects/api/constants.py index 0765673b..6e4e61fa 100644 --- a/src/objects/api/constants.py +++ b/src/objects/api/constants.py @@ -9,3 +9,4 @@ class Operators(models.TextChoices): lt = "lt", _("lower than") lte = "lte", _("lower than or equal to") icontains = "icontains", _("case-insensitive partial match") + in_list = "in", _("contains") diff --git a/src/objects/api/v1/filters.py b/src/objects/api/v1/filters.py index bb36d9f6..4bdb24ac 100644 --- a/src/objects/api/v1/filters.py +++ b/src/objects/api/v1/filters.py @@ -101,6 +101,10 @@ def filter_data_attrs(self, queryset, name, value: str): queryset = queryset.filter( **{f"data__{variable}__icontains": str_value} ) + elif operator == "in": + # in must be a list + values = str_value.split("|") + queryset = queryset.filter(**{f"data__{variable}__in": values}) else: # gt, gte, lt, lte operators diff --git a/src/objects/api/v2/filters.py b/src/objects/api/v2/filters.py index cccdc19a..785d8fa8 100644 --- a/src/objects/api/v2/filters.py +++ b/src/objects/api/v2/filters.py @@ -109,6 +109,10 @@ def filter_data_attrs(self, queryset, name, value: str): queryset = queryset.filter( **{f"data__{variable}__icontains": str_value} ) + elif operator == "in": + # in must be a list + values = str_value.split("|") + queryset = queryset.filter(**{f"data__{variable}__in": values}) else: # gt, gte, lt, lte operators diff --git a/src/objects/api/validators.py b/src/objects/api/validators.py index f36dd06c..50c2cd2c 100644 --- a/src/objects/api/validators.py +++ b/src/objects/api/validators.py @@ -81,9 +81,11 @@ def validate_data_attrs(value: str): } raise serializers.ValidationError(message, code=code) - if operator not in (Operators.exact, Operators.icontains) and isinstance( - string_to_value(val), str - ): + 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} diff --git a/src/objects/tests/v1/test_filters.py b/src/objects/tests/v1/test_filters.py index c337ef93..5c74a3c5 100644 --- a/src/objects/tests/v1/test_filters.py +++ b/src/objects/tests/v1/test_filters.py @@ -328,6 +328,31 @@ def test_filter_exclude_old_records(self): data = response.json() self.assertEqual(len(data), 0) + def test_filter_in_string(self): + record = ObjectRecordFactory.create( + data={"name": "demo1"}, object__object_type=self.object_type + ) + record2 = ObjectRecordFactory.create( + data={"name": "demo2"}, object__object_type=self.object_type + ) + ObjectRecordFactory.create( + data={"name": "demo3"}, object__object_type=self.object_type + ) + + response = self.client.get(self.url, {"data_attrs": "name__in__demo1|demo2"}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + self.assertEqual(len(data), 2) + self.assertEqual( + data[0]["url"], + f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", + ) + self.assertEqual( + data[1]["url"], + f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", + ) + class FilterDateTests(TokenAuthMixin, APITestCase): @classmethod diff --git a/src/objects/tests/v2/test_filters.py b/src/objects/tests/v2/test_filters.py index 52ed5ddc..186cf0ea 100644 --- a/src/objects/tests/v2/test_filters.py +++ b/src/objects/tests/v2/test_filters.py @@ -400,6 +400,33 @@ def test_filter_date_field_gte(self): self.assertEqual(len(data), 0) + def test_filter_in_string(self): + record = ObjectRecordFactory.create( + data={"name": "demo1"}, object__object_type=self.object_type + ) + record2 = ObjectRecordFactory.create( + data={"name": "demo2"}, object__object_type=self.object_type + ) + ObjectRecordFactory.create( + data={"name": "demo3"}, object__object_type=self.object_type + ) + + response = self.client.get(self.url, {"data_attrs": "name__in__demo1|demo2"}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json()["results"] + + self.assertEqual(len(data), 2) + self.assertEqual( + data[0]["url"], + f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", + ) + self.assertEqual( + data[1]["url"], + f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", + ) + class FilterDateTests(TokenAuthMixin, APITestCase): @classmethod From 322a5cb8f18f8db4a9ee3ffdacd8a90e169c4839 Mon Sep 17 00:00:00 2001 From: Conor Holden Date: Tue, 27 Aug 2024 17:32:21 +0200 Subject: [PATCH 2/3] :memo:[#414] add in operator to schema --- src/objects/api/constants.py | 2 +- src/objects/api/v1/openapi.yaml | 2 ++ src/objects/api/v2/openapi.yaml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/objects/api/constants.py b/src/objects/api/constants.py index 6e4e61fa..a85a012e 100644 --- a/src/objects/api/constants.py +++ b/src/objects/api/constants.py @@ -9,4 +9,4 @@ class Operators(models.TextChoices): lt = "lt", _("lower than") lte = "lte", _("lower than or equal to") icontains = "icontains", _("case-insensitive partial match") - in_list = "in", _("contains") + in_list = "in", _("in a list of values seperated by `|`") diff --git a/src/objects/api/v1/openapi.yaml b/src/objects/api/v1/openapi.yaml index b86a6b24..00b6cc04 100644 --- a/src/objects/api/v1/openapi.yaml +++ b/src/objects/api/v1/openapi.yaml @@ -106,6 +106,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match + * `in` - in a list of values seperated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. @@ -537,6 +538,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match + * `in` - in a list of values seperated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index a2d5409b..e9585683 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -106,6 +106,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match + * `in` - in a list of values seperated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. @@ -587,6 +588,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match + * `in` - in a list of values seperated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. From 4dc90a28f31c4a176071db38a2d1cf9566414601 Mon Sep 17 00:00:00 2001 From: Conor Holden Date: Fri, 30 Aug 2024 10:09:49 +0200 Subject: [PATCH 3/3] :pencil2:[#414] fix typo Co-authored-by: Steven Bal --- src/objects/api/constants.py | 2 +- src/objects/api/v1/openapi.yaml | 4 ++-- src/objects/api/v2/openapi.yaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/objects/api/constants.py b/src/objects/api/constants.py index a85a012e..31f3bb1b 100644 --- a/src/objects/api/constants.py +++ b/src/objects/api/constants.py @@ -9,4 +9,4 @@ class Operators(models.TextChoices): lt = "lt", _("lower than") lte = "lte", _("lower than or equal to") icontains = "icontains", _("case-insensitive partial match") - in_list = "in", _("in a list of values seperated by `|`") + in_list = "in", _("in a list of values separated by `|`") diff --git a/src/objects/api/v1/openapi.yaml b/src/objects/api/v1/openapi.yaml index 00b6cc04..22c70e7f 100644 --- a/src/objects/api/v1/openapi.yaml +++ b/src/objects/api/v1/openapi.yaml @@ -106,7 +106,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match - * `in` - in a list of values seperated by `|` + * `in` - in a list of values separated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. @@ -538,7 +538,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match - * `in` - in a list of values seperated by `|` + * `in` - in a list of values separated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index e9585683..d37679d7 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -106,7 +106,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match - * `in` - in a list of values seperated by `|` + * `in` - in a list of values separated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. @@ -588,7 +588,7 @@ paths: * `lt` - lower than * `lte` - lower than or equal to * `icontains` - case-insensitive partial match - * `in` - in a list of values seperated by `|` + * `in` - in a list of values separated by `|` `value` may not contain double underscore or comma characters. `key` may not contain comma characters and includes double underscore only if it indicates nested attributes.