Closed henryfool91 closed 1 year ago
Hello @henryfool91 ,
What you are describing can be implemented with the existing api doing something like this:
from django.db.models import Q
from strawberry import UNSET
from strawberry_django.filters import lookup_name_conversion_map
Z = TypeVar("Z")
def build_or_filter(filters):
_filter = Q(pk=None)
fields = getattr(filters, 'fields')
for _field in fields:
field = getattr(_field , 'field')
lookup = getattr(_field, 'lookup')
for plu in lookup._type_definition.fields:
_lookup = plu.name
lu_val = getattr(lookup , _lookup)
if lu_val is not UNSET:
if _lookup in lookup_name_conversion_map:
_lookup = lookup_name_conversion_map[_lookup]
_filter |= Q(** { f"{field}__{_lookup}" : f"{lu_val}" })
return _filter
#Any of these that make sense for your use case
@strawberry.input
class CustomFilterLookup(Generic[Z]):
exact: Optional[Z] = UNSET
i_exact: Optional[Z] = UNSET
contains: Optional[Z] = UNSET
i_contains: Optional[Z] = UNSET
in_list: Optional[List[Z]] = UNSET
gt: Optional[Z] = UNSET
gte: Optional[Z] = UNSET
lt: Optional[Z] = UNSET
lte: Optional[Z] = UNSET
starts_with: Optional[Z] = UNSET
i_starts_with: Optional[Z] = UNSET
ends_with: Optional[Z] = UNSET
i_ends_with: Optional[Z] = UNSET
range: Optional[List[Z]] = UNSET
is_null: Optional[bool] = UNSET
regex: Optional[str] = UNSET
i_regex: Optional[str] = UNSET
@strawberry.input
class FieldWithFilter(Generic[Z]):
field: str
lookup: CustomFilterLookup[Z]
@strawberry.input
class FieldsOrFilter(Generic[Z]):
fields: List[ FieldWithFilter[Z]]
@strawberry.django.filters.filter(MyModel,lookups=True)
class MyModelFilter:
model_field: auto
other_model_field: auto
fields_or_filter: FieldsOrFilter[str]
def filter_fields_or_filter(self,queryset):
_filter = build_or_filter(self.fields_or_filter)
return queryset.filter(_filter)
You can then query normally with the 3 fields joined together with AND and the internals of fields_or_filter with OR. Something like this:
{
myField(
filters: {
fieldsOrFilter: {
fields : [
{
field: "some_model_field",
lookup: { iContains: "ska"}
},
{
field: "some_other_model_field",
lookup: {iContains: "ATA"}
}
]
},
model_field: { gt: 100 },
other_model_field: { lte: 40 }
}
) {
...myFieldFragment
}
@bellini666 , or anyone who might know. Is this already somehow implemented into the library and I missed it? If I am reinventing the wheel here please give me a heads up. Could not find anything like this in the docs or issues here. Just a PR proposal describing something like this. Otherwise I would love to see something like this included in future versions.
@m4riok this is not implemented in the lib. The only problem I see there is that it totally transforms the AND
in OR
, so you have to choose between one and another. It is not possible to do something more complex like foo AND (bar OR baz)
To be able to handle more complex cases I think we will need to do a major refactor of the implementation, with possible breaking changes
@bellini666 it only transforms AND to OR for everything defined inside fieldsOrFilter
. The model_field
and other_model_field
in the above example retain their AND
logic and are joined with fieldsOrFilter
via AND
. The example above would produce the following statement:
Q(model_field__gt=100) &
Q(other_model_field__lte=40) &
( Q(some_other_model_field__icontains='ATA') | Q(some_model_field__icontains='ska')
And if you want to have multiple OR
blocks joined together with AND
logic along with the already provided lookups for the model fields you could define multiple instances of FieldsOrFilter[Generic[T]]
like so:
@strawberry.django.filters.filter(MyModel,lookups=True)
class MyModelFilter:
model_field: auto
other_model_field: auto
fields_or_filter: FieldsOrFilter[str]
another_or_filter: FieldsOrFilter[int]
def filter_fields_or_filter(self,queryset):
_filter = build_or_filter(self.fields_or_filter)
return queryset.filter(_filter)
def filter_another_or_filter(self,queryset):
_filter = build_or_filter(self.another_or_filter)
return queryset.filter(_filter)
Or it all can be made more generic like this:
@strawberry.input
class FieldsOrFilter(Generic[Z]):
fields: List[ FieldWithFilter[Z]]
logic: str
@strawberry.input
class MultipleNestedOrFiltersWithSelectableLogic(Generic[Z]):
blocks: List[FieldsOrFilter[Z]]
And just introduce a resolver for this filter type that would add another loop to unpack the blocks
list and apply the logic
selected by the user between them
The only bad thing about this is that it allows for a single level of nesting. You cannot do overly complex logic like X and Y and ( Z or ( not K and not M))
or anything with more than one level of nested statements.
But imho supporting more than 1 level of nested expressions in filters provided by the library is sort of an overkill. Most people having a need for such functionality could implement their own filter resolver. But a block of OR
statements joined via AND
with the existing model field lookups is a very common use case.
This was fixed in https://github.com/strawberry-graphql/strawberry-graphql-django/releases/tag/v0.14.0 :)
Hello everyone, thanks for this great repository, start studying GraphQL with strawberry, and have no regrets so far! I have a question about filters - is there a way to use "or" logic or Django Q object in filters? Intresting about filtering several fields, like
Or may be something like
Thanks in advance!
Upvote & Fund