How to apply nulls_last ordering to an extra select field in Django?

Viewed 30

I'm trying to apply order_by() with nulls_last=True to a queryset:

MyModel.obects.all().order_by(*[models.F('field').desc(nulls_last=True)])

This works fine for regular fields, but it throws an error when I try to order by an extra field in the same manner:

queryset = MyModel.obects.extra(**{'select': {'extra_field': 'custom_sql'}}).all()
queryset = queryset.order_by(*[models.F('extra_field').desc(nulls_last=True)])

The problem is it is unable to find extra_field column when using models.F(). If I simply use order_by('extra_field') as a string it works because Django is doing some extra preprocessing and handles extra fields differently. After digging through the sources I was able to make it work as:

queryset = MyModel.obects.extra(**{'select': {'extra_field': 'custom_sql'}}).all()
queryset = queryset.order_by(*[models.expressions.Ref('extra_field', models.expressions.RawSQL('custom_sql', [])).desc(nulls_last=True)])

The downside of this solution is that I have to treat regular fields and extra fields differently, and I have to repeat the custom_query inside order_by.

I am trying to build a universal function that would simply take a list of fields as stings (how Django would take) and wrap them with nulls_last.

The question is: Given only a field name as a string and a queryset, is there a universal way to apply nulls_last to it without specifying if this is a native field or an extra, and without passing a custom query for an extra field? I am trying to make the function signature to be something like:

def apply_order_by_nulls_last(queryset, field:str):
    #detect field type and convert it to OrderBy expression with nulls_last
    ...
    return queryset.order_by(...)
0 Answers
Related