I have a Graphene interface with Relay and filters. It works pretty well but I would like to add the order_by options. My objects look like:
class FooGQLType(DjangoObjectType):
class Meta:
model = Foo
exclude_fields = ('internal_id',)
interfaces = (graphene.relay.Node,)
filter_fields = {
"id": ["exact"],
"code": ["exact", "icontains"],
}
connection_class = ExtendedConnection
class Query(graphene.ObjectType):
foo = DjangoFilterConnectionField(FooGQLType)
ExtendedConnection should not be relevant but:
class ExtendedConnection(graphene.Connection):
class Meta:
abstract = True
total_count = graphene.Int()
def resolve_total_count(root, info, **kwargs):
return root.length
This allows me to query like foo(code_Icontains:"bar")
.
According to the Graphene documentation I should be using the OrderingFilter in a FilterSet for that. I find it a bit annoying since the filters are supposed to be automatic but if I do:
class FooGQLFilter(FilterSet):
class Meta:
model = Foo
order_by = OrderingFilter(
fields=(
('code', 'code'),
('lastName', 'last_name'),
('otherNames', 'other_names'),
)
)
I get an error that I need to provide fields
or exclude
:
AssertionError: Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' has been deprecated since 0.15.0 and is now disallowed. Add an explicit 'Meta.fields' or 'Meta.exclude' to the FooGQLFilter class.
So if I add a fields = []
to silence it, it compiles.
However, when I use it in:
foo = DjangoFilterConnectionField(FooGQLType, filterset_class=FooGQLFilter)
My regular filters like code_Icontains
vanish. I could add them again over there but it's silly. From a quick look at the source, it looks like Relay or django-filters already created a FilterSet class (makes sense) and overwriting it this way is obviously a poor idea.
How do I add the orderBy filter on my Graphene Relay filtered objects ? I feel like this should be pretty straightforward but I am struggling to figure this out.
I have also seen examples subclassing DjangoFilterConnectionField
with a connection_resolver
that injects the order_by somehow but that tells me that there is no orderBy parameter.
I have adapted a solution from a GitHub issue on this topic:
from graphene_django.filter import DjangoFilterConnectionField
from graphene.utils.str_converters import to_snake_case
class OrderedDjangoFilterConnectionField(DjangoFilterConnectionField):
"""
Adapted from https://github.com/graphql-python/graphene/issues/251
Substituting:
`claims = DjangoFilterConnectionField(ClaimsGraphQLType)`
with:
```
claims = OrderedDjangoFilterConnectionField(ClaimsGraphQLType,
orderBy=graphene.List(of_type=graphene.String))
```
"""
@classmethod
def connection_resolver(cls, resolver, connection, default_manager, max_limit,
enforce_first_or_last, filterset_class, filtering_args,
root, info, **args):
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
qs = filterset_class(
data=filter_kwargs,
queryset=default_manager.get_queryset(),
request=info.context
).qs
order = args.get('orderBy', None)
if order:
if type(order) is str:
snake_order = to_snake_case(order)
else:
snake_order = [to_snake_case(o) for o in order]
qs = qs.order_by(*snake_order)
return super(DjangoFilterConnectionField, cls).connection_resolver(
resolver,
connection,
qs,
max_limit,
enforce_first_or_last,
root,
info,
**args
)
To use it, just adapt the Query from:
claims = DjangoFilterConnectionField(ClaimsGraphQLType)
to
claims = OrderedDjangoFilterConnectionField(ClaimsGraphQLType,
orderBy=graphene.List(of_type=graphene.String))
And you can then query:
{ claims(status: 2, orderBy: "-id") { id } }
or
{ claims(status: 2, orderBy: ["creationDate", "lastName"]) { id } }