devind-team / graphene-django-filter

Advanced filters for Graphene
MIT License
19 stars 7 forks source link

Full text search API improvement #29

Open SquakR opened 2 years ago

SquakR commented 2 years ago

Problem

The current implementation of the full text search introduces new filter classes that extends the Filter class of the django_filters library. Using such filters in a FilterSet class is inconvenient and confusing because they must have specific field names and lookup expressions. For example, the manual implementation of the trigram filter for a name field looks like the one below because of FloatLookupsInputType is the constant.

input FloatLookupsInputType {
  exact: Float
  gt: Float
  gte: Float
  lt: Float
  lte: Float
}
from graphene_django_filter import AdvancedFilterSet
from graphene_django_filter.filters import TrigramFilter

class ManualFilterSet(AdvancedFilterSet):
    name__trigram = TrigramFilter(field_name='name__trigram', lookup_expr='exact')
    name__trigram__gt = TrigramFilter(field_name='name__trigram', lookup_expr='gt')
    name__trigram__gte = TrigramFilter(field_name='name__trigram', lookup_expr='gte')
    name__trigram__lt = TrigramFilter(field_name='name__trigram', lookup_expr='lt')
    name__trigram__lte = TrigramFilter(field_name='name__trigram', lookup_expr='lte')

This is so confusing and verbose that it is not documented because it is always expected to use filters generation with the fields Meta field. For example.

from graphene_django_filter import AdvancedFilterSet

class AutoFilterSet(AdvancedFilterSet):
    class Meta:
        fields = {
            'name': ('full_text_search',)
        }

In general, the approach described above works, but it contradicts the logic of building the django_filters library. The 'full_text_search' is not a lookup expression but is used as such expression in the fields dictionary. The name__trigram is not a field name but is used as field name in the TrigramFilter class. The reasons for this approach were to try to reuse code as much as possible and avoid having to implement an additional Advanced DjangoObjectType that extends the DjangoObjectType class.

Suggested solution

The new API should look like this.

from graphene_django_filter import AdvancedFilterSet

class AutoFilterSet(AdvancedFilterSet):
    class Meta:
       full_text_search = {
           'trigram': {
                'name': ('exact', 'lt', 'gt',)
            },
            search_query: ('name', 'email')
            search_rank: {
                'fields': ('name', 'email')
                'lookups': ('exact', 'lt', 'gt',)
            }
       }

The logic of applying filtering and annotations will be moved to the level of the AdvancedFilterSet class, as well as filters of different nature will receive logical settings through a special field of the Meta class. Filters inherited from the Filter class will no longer be explicitly used for filtering with full-text search, so their manual application will no longer be possible.

Caveat

This change requires an implementation of the AdvancedDjangoObjectType class, because the standard DjangoObjectType class will ignore the unknown meta key.

from graphene_django_filter import AdvancedDjangoObjectType

class UserType(AdvancedDjangoObjectType):
    class Meta:'
       model = User
       fields = (
           'id',
           'name',
           'email',
       )
       filter_fields = {
           'name': ('exact',) 
       }
       full_text_search = {
           'trigram': {
                'name': ('exact', 'lt', 'gt',)
            },
            search_query: ('name', 'email')
            search_rank: {
                'fields': ('name', 'email')
                'lookups': ('exact', 'lt', 'gt',)
            }
       }