strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
415 stars 120 forks source link

Custom ordering methods? #236

Closed vitosamson closed 7 months ago

vitosamson commented 1 year ago

Feature Request Type

Description

I'm wondering if you'd consider supporting custom methods on ordering.order classes, in the same way you do for filters.filter classes. Something like:

@ordering.order(SomeModel)
class SomeModelOrder:
    related_thing: auto

    def order_related_thing(self, queryset):
        return queryset.order_by('related_thing__name')

This would be really useful when you want to order by some related model, but don't necessarily want to expose that relation and expect the client to know about it. In the example above, we're exposing related_thing__name as related_thing without leaking the db architecture details. It's also really handy if you need to annotate the queryset before ordering on it, e.g. for things like computed time deltas (days_in_progress and such).

I'm currently doing the following to implement this, but it only works in custom resolvers and not when the order class is passed to either the type or the field definition:

class ModelOrder:
    def apply(self, queryset: QuerySet) -> QuerySet:
        for field in fields(self):
            if getattr(self, field.name, strawberry.UNSET) == strawberry.UNSET:
                continue

            order_method = getattr(self, f"order_{field.name}", None)

            if order_method:
                queryset = order_method(queryset)
                # we expect the custom method to do the ordering, so we want to prevent strawberry's ordering handler
                # from doing anything with it since the field name may not be valid on this model
                setattr(self, field.name, strawberry.UNSET)

        return ordering.apply(self, queryset=queryset)

@ordering.order(models.Report)
class ReportOrder(ModelOrder):
    product_type: auto

    def order_product_type(self, queryset):
        return queryset.order_by("requisition__product_type")

def reports_resolver(order: ReportOrder) -> list[Report]:
    return order.apply(models.Report.objects.all())

Upvote & Fund

Fund with Polar

bellini666 commented 1 year ago

Yes. I actually am planning on redoing the ordering functionality because the current one has a lot of issues. For example, there's no way for you to ask for 2 or more ordering in a given sequence, since the sequence used will be the ones defined by the attributes.

Together with that I also want to make it possible to have custom orderings, just like filters.

OdysseyJ commented 1 year ago

@bellini666 Thank you for considering nulls_last ordering also! wo = Work_Order.objects.order_by(F('dateWORequired').asc(nulls_last=True))

bellini666 commented 7 months ago

https://github.com/strawberry-graphql/strawberry-django/pull/478