Skip to content

Allow case-insensitive ordering / support lookups in Column.order_by #1007

@rixx

Description

@rixx

Django has by now made it easy to perform case-insensitive-ish ordering of a queryset by running queryset.order_by(Lower("title")), or by using Lower("title").desc() for reverse ordering.

I'd love to do this with django-tables2, but seeing as you have to pass a string to the order_by field, I can't see an easy way to do so. I think using database functions (particularly Lower()) in sorting is common enough that it would be nice to support it out of the box, so that as a django-tables2 user, I could define a column as tables.Column(order_by=(Lower("name"), "pk").

I'm currently doing this by using my own Column subclass in most places. This isn't too bad, but as the implementation took some attempts to get right, I thought I'd share it here and ask for the feature in core :)

class FunctionOrderMixin:
    order_by_function_lookup = {}

    def __init__(self, *args, order_by=None, **kwargs):
        if order_by and not isinstance(order_by, str):
            if isinstance(order_by, Transform):
                order_by = (order_by, )
            # Pass a plain string list to Column so that it doesn't complain
            plain_order_by = []
            for key in order_by:
                if isinstance(key, Transform):
                    plain_field = key.source_expressions[0].name
                    self.order_function_lookup[plain_field] = key
                    plain_order_by.append(plain_field)
            order_by = plain_order_by

        super().__init__(*args, order_by=order_by, **kwargs)

    def order(self, queryset, is_descending):
        if not self.order_function_lookup:
            return (queryset, False)
        mapped_order_by = []
        for field in self.order_by:
            if func := self.order_by_function_lookup.get(field):
                if is_descending:
                    func = func.desc()
                mapped_order_by.append(func)
            else:
                mapped_order_by.append(field)
        queryset = queryset.order_by(*mapped_order_by)
        return (queryset, True)


class SortableColumn(FunctionOrderMixin, tables.Column):
    pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions