From b6c393e51fc622facc5d8e5cf5bda34a0df3a7f6 Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Mon, 1 Jul 2024 09:05:00 +0200 Subject: [PATCH] refactor(utils)!: drop `filtermethods` The `utils.filtermethods` module contained a couple of unused filtermethods that were used in old APIS models and referenced hardcoded field names. They were all replaced by more flexible approaches by now. The `construct_lookup` method that was part of the module can be useful and was therefore salvaged and moved to the `utils.helpers` module. The `test_filtermethods` module was renamed accordingly. Closes: #790 --- apis_core/utils/filtermethods.py | 113 ------------------ apis_core/utils/helpers.py | 36 ++++++ ...{test_filtermethods.py => test_helpers.py} | 2 +- 3 files changed, 37 insertions(+), 114 deletions(-) delete mode 100644 apis_core/utils/filtermethods.py rename apis_core/utils/{test_filtermethods.py => test_helpers.py} (91%) diff --git a/apis_core/utils/filtermethods.py b/apis_core/utils/filtermethods.py deleted file mode 100644 index 2e49acf38..000000000 --- a/apis_core/utils/filtermethods.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -This module contains filter functions that can be used by django-filter filters -See https://django-filter.readthedocs.io/en/main/ref/filters.html#method -""" - -from django.db.models import Q - - -# should return tuple[str, str] once we are >=3.9 -def construct_lookup(value: str) -> tuple: - """ - Helper method to parse input values and construct field lookups - (https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups) - Parses user input for wildcards and returns a tuple containing the - interpreted django lookup string and the trimmed value - E.g. - - - ``example`` -> ``('__icontains', 'example')`` - - ``*example*`` -> ``('__icontains', 'example')`` - - ``*example`` -> ``('__iendswith', 'example')`` - - ``example*``-> ``('__istartswith', 'example')`` - - ``"example"`` -> ``('__iexact', 'example')`` - - :param str value: text to be parsed for ``*`` - :return: a tuple containing the lookup type and the value without modifiers - """ - - if value.startswith("*") and not value.endswith("*"): - value = value[1:] - return "__iendswith", value - - elif not value.startswith("*") and value.endswith("*"): - value = value[:-1] - return "__istartswith", value - - elif value.startswith('"') and value.endswith('"'): - value = value[1:-1] - return "__iexact", value - - else: - if value.startswith("*") and value.endswith("*"): - value = value[1:-1] - return "__icontains", value - - -def flexible_string(queryset, name, value): - """ - filter method for filtering arbitrary fields using :func:`construct_lookup` - """ - lookup, value = construct_lookup(value) - return queryset.filter(**{name + lookup: value}) - - -# filtermethods for specific usecases: - - -# TODO RDF: Check if this should be removed or adapted -def related_entity_name(queryset, name, value): - """ - filter on the name of a related entity using :func:`construct_lookup` - """ - lookup, value = construct_lookup(value) - - queryset = queryset.filter( - Q(**{f"triple_set_from_obj__subj__name{lookup}": value}) - | Q(**{f"triple_set_from_subj__obj__name{lookup}": value}) - ).distinct() - - return queryset - - -def related_property_name(queryset, name, value): - """ - filter on the name of a related property using :func:`construct_lookup` - """ - lookup, value = construct_lookup(value) - - queryset = queryset.filter( - Q(**{f"triple_set_from_obj__prop__name_forward{lookup}": value}) - | Q(**{f"triple_set_from_obj__prop__name_reverse{lookup}": value}) - | Q(**{f"triple_set_from_subj__prop__name_forward{lookup}": value}) - | Q(**{f"triple_set_from_subj__prop__name_reverse{lookup}": value}) - ).distinct() - - return queryset - - -def related_arbitrary_model_name(queryset, name, value): - """ - Searches through an arbitrarily related model on its name field using :func:`construct_lookup`. - Note that this works only if - - - the related model has a field 'name' - - the filter using this method has the same name as the field of the model on which the filter is applied. - - (E.g. the field ``profession`` on a person relates to another model: the professiontype. Here the filter on a person - must also be called ``profession`` as the field ``profession`` exists within the person model and is then used to search in. - Using this example of professions, such a lookup would be generated: ``Person.objects.filter(profession__name__... )`` ) - """ - - lookup, value = construct_lookup(value) - - # name variable is the name of the filter and needs the corresponding field within the model - return queryset.filter(**{name + "__name" + lookup: value}) - - -def name_label_filter(queryset, name, value): - lookup, value = construct_lookup(value) - - queryset_related_label = queryset.filter(**{"label__label" + lookup: value}) - queryset_self_name = queryset.filter(**{name + lookup: value}) - - return (queryset_related_label | queryset_self_name).distinct().all() diff --git a/apis_core/utils/helpers.py b/apis_core/utils/helpers.py index 45c87e277..2a616408c 100644 --- a/apis_core/utils/helpers.py +++ b/apis_core/utils/helpers.py @@ -202,3 +202,39 @@ def style_insert(text): if show_a: result += style_remove(a[a_start:a_end]) return result + + +def construct_lookup(value: str) -> tuple[str, str]: + """ + Helper method to parse input values and construct field lookups + (https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups) + Parses user input for wildcards and returns a tuple containing the + interpreted django lookup string and the trimmed value + E.g. + + - ``example`` -> ``('__icontains', 'example')`` + - ``*example*`` -> ``('__icontains', 'example')`` + - ``*example`` -> ``('__iendswith', 'example')`` + - ``example*``-> ``('__istartswith', 'example')`` + - ``"example"`` -> ``('__iexact', 'example')`` + + :param str value: text to be parsed for ``*`` + :return: a tuple containing the lookup type and the value without modifiers + """ + + if value.startswith("*") and not value.endswith("*"): + value = value[1:] + return "__iendswith", value + + elif not value.startswith("*") and value.endswith("*"): + value = value[:-1] + return "__istartswith", value + + elif value.startswith('"') and value.endswith('"'): + value = value[1:-1] + return "__iexact", value + + else: + if value.startswith("*") and value.endswith("*"): + value = value[1:-1] + return "__icontains", value diff --git a/apis_core/utils/test_filtermethods.py b/apis_core/utils/test_helpers.py similarity index 91% rename from apis_core/utils/test_filtermethods.py rename to apis_core/utils/test_helpers.py index 6bcddb150..4a4952ce8 100644 --- a/apis_core/utils/test_filtermethods.py +++ b/apis_core/utils/test_helpers.py @@ -3,7 +3,7 @@ from django.test import TestCase -from .filtermethods import construct_lookup +from .helpers import construct_lookup lookups = { "*foo*": ("__icontains", "foo"),