strawberry-django icon indicating copy to clipboard operation
strawberry-django copied to clipboard

Custom ordering methods?

Open vitosamson opened this issue 2 years ago • 2 comments

Feature Request Type

  • [ ] Core functionality
  • [X] Alteration (enhancement/optimization) of existing feature(s)
  • [X] New behavior

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

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

vitosamson avatar Feb 20 '23 21:02 vitosamson

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.

bellini666 avatar Feb 25 '23 19:02 bellini666

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

OdysseyJ avatar Mar 09 '23 03:03 OdysseyJ

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

bellini666 avatar Mar 18 '24 21:03 bellini666