Open insomniac34 opened 1 year ago
Hey @insomniac34 . Unfortunatly not yet. The filtering/ordering code is based on the functionality provided by https://github.com/strawberry-graphql/strawberry-graphql-django and it doesn't provide that.
I'm actually planning on creating a new ordering functionality to solve not only that issue, but also others that are not so apparent. For example, there's no way for you to choose the order of the orderings when defining more than one
Thanks for the clarification @bellini666
I made a working patch for having ordered order-by clauses, but if @bellini666 has something prepared for this, it will be probably better. If you really need this now @insomniac34, tell me and I’ll post my dirty patch here :-)
@cpontvieux-systra I did not start working on it yet, I only have what I want in mind and how to execute it =P.
But feel free to post your patch here and we can discuss the implementation :)
My patch is in two steps.
First, I patch the order
decorator:
from strawberry_django.ordering import order as _order
ORDER_FIELDS_NAME = 'order'
def order(model):
def wrapper(cls):
cls.__annotations__[ORDER_FIELDS_NAME] = list[str]
setattr(cls, ORDER_FIELDS_NAME, None)
return _order(model)(cls)
return wrapper
This will add an order
field to the schema of type list[str]
to any Order type defined. Maybe the name order
is bad chosen, I don’t know.
Then the django field using the order type needs to be patched too (it’s a bigger patch as multiple functions need to be patched):
from functools import partial
from django.db.models import QuerySet
from strawberry.utils.str_converters import TO_KEBAB_CASE_RE as TO_SNAKE_CASE_RE
from strawberry_django.fields.field import StrawberryDjangoField
from strawberry_django.ordering import Ordering
from strawberry_django.utils import fields
from strawberry_django_plus import gql
def ordering_orderby_patch(django_field: StrawberryDjangoField) -> None:
"""
Allow to use an `order` field in order type to order ascending/descending conditions by name.
"""
def to_snake_case(name: str) -> str:
return TO_SNAKE_CASE_RE.sub(r'_\1', name).lower()
def order_order_args(order, args: set[str]) -> list[str]:
ordering_value = getattr(order, ORDER_FIELDS_NAME, gql.UNSET)
if ordering_value is gql.UNSET:
ordering_value = []
order_list = [to_snake_case(name) for name in ordering_value]
ordered_args: list[str] = []
for field in order_list:
if field in args:
ordered_args.append(field)
args.remove(field)
elif (desc_field := f'-{field}') in args:
ordered_args.append(desc_field)
args.remove(desc_field)
ordered_args.extend(args)
return ordered_args
def generate_order_args(order, prefix="") -> set[str]:
args = set()
for field in fields(order):
ordering = getattr(order, field.name, gql.UNSET)
if field.name == ORDER_FIELDS_NAME:
continue
if ordering is gql.UNSET:
continue
if ordering == Ordering.ASC:
args.add(f"{prefix}{field.name}")
elif ordering == Ordering.DESC:
args.add(f"-{prefix}{field.name}")
else:
subargs = generate_order_args(ordering, prefix=f"{prefix}{field.name}__")
args.update(subargs)
return args
def apply_order(self, queryset: QuerySet, order) -> QuerySet:
if order is gql.UNSET or order is None:
return queryset
args = order_order_args(order, generate_order_args(order))
return queryset.order_by(*args) if args else queryset
setattr(django_field, 'apply_order', partial(apply_order, django_field))
Then you define your order as usual but use the patched order
decorator and you need to patch any strawberry django field:
@order(models.YourModel)
class YourModelOrder:
name: auto
priority: auto
@gql.django.type(models.YourModel, order=YourModelOrder)
class YourModel:
id: auto
name: auto
priority: auto
all_yourmodels = gql.django.field()
# it’s ok to patch any gql.django.field, it does not harm if your type/field does not have an order class
ordering_orderby_patch(all_yourmodels)
@gql.type
class Query:
all_yourmodels: list[YourModel] = all_yourmodels
Then in your query:
query {
allYourmodels (order: {name: ASC, priority: DESC, order: ["priority", "name"]}) {
id
name
priority
}
}
Most of this code is already in strawberry_django.ordering
. As I say, it’s a dirty patch.
On better way would also to have sorted Ordering
attributes before any transformation. I may try to improve that.
I’m also not sure order
is that smart of a name. If one have an attribute named order
and want to sort against it, it will clash. _order
could also be an option as it translates to Order
in the graphQL Schema and so the capitalization can make it clear that it’s a different kind.
There is also no checking for the values in order
. Exceptions should be raised if a non order attribute is used there.
That's an interesting solution, and probably the only way to enforce the requested ordering order currently.
What I was thinking was to actually deprecate the current order
and define an ordering
. That ordering
would receive a list of objects from the current order type, and since graphql doesn't support input type unions, we could use the oneOf directive which is going to be supported on strawberry when this PR gets merged. That way we can enforce the ordering order.
With that I also want to make it easier to define custom resolvers for both ordering and filtering. Currently you have to define a resolve_<attr>
field, which can lead to errors if you mistype it. I want to create a decorator to define those for both cases.
Hello!
So, I have a Django model,
Invoice
, which let's say has two fields,id
andamount
, a float.Likewise, I use Strawberry Django Plus to define a custom Filter class for this (yes I know it's not particularly efficient, but it does work):
In the above Filter definition, I am able to define custom filtering logic for the Invoice, and I have access to my custom Django InvoiceManager.
Similarly, I am trying to implement custom sorting logic in my
InvoiceOrder
class, in the same fashion as the above filter snippet; however I can't find any documentation or examples on how to do this. Here is what I have so far, which is failing withcannot resolve keyword 'amount'. Choices are: 'id'.
no matter what I try to return fromorder_amount
.Is there a way for me to define custom sorting logic in a
gql.django.ordering.order
class?thank you!