carltongibson / django-filter

A generic system for filtering Django QuerySets based on user selections
https://django-filter.readthedocs.io/en/main/
Other
4.46k stars 769 forks source link

Ordering by multiple fields #274

Closed ad-m closed 9 years ago

ad-m commented 9 years ago

Hello,

I am would like to ask about support ordering by multiple fields at once like: Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline') I can see it fail:

class StaffCaseFilter(django_filters.FilterSet):
    class Meta:
        model = Case
        fields = ['status', 'client', 'name', 'created_on', 'last_send', 'last_action']
        order_by = (
            (('deadline', '-pk'), _('Dead-line')),
        )

Result:

Traceback (most recent call last):
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django/views/generic/base.py", line 69, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/braces/views/_access.py", line 64, in dispatch
    request, *args, **kwargs)
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django/views/generic/base.py", line 87, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django_filters/views.py", line 61, in get
    self.object_list = self.filterset.qs
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django_filters/filterset.py", line 376, in qs
    qs = qs.order_by(*self.get_order_by(ordered_value))
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django/db/models/query.py", line 814, in order_by
    obj.query.add_ordering(*field_names)
  File "/home/adas/.virtualenvs/poradnia/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1639, in add_ordering
    if not ORDER_PATTERN.match(item):
TypeError: expected string or buffer

Is there a easy way to obtain such a result?

Greetings,

ad-m commented 9 years ago

Oh, here is it:


class StaffCaseFilter(CrispyFilterMixin, django_filters.FilterSet):
    name = django_filters.CharFilter(label=_("Subject"), lookup_type='icontains')
    client = UserChoiceFilter(label=_("Client"))
    created_by = UserChoiceFilter(label=_("Created by"))
    created_on = django_filters.DateRangeFilter(label=_("Created on"))
    last_send = django_filters.DateRangeFilter(label=_("Last send"))
    last_action = django_filters.DateRangeFilter(label=_("Last action"))

    class Meta:
        model = Case
        fields = ['status', 'client', 'name', 'created_on', 'last_send', 'last_action']
        order_by = (
            ('default', _('Default')),
            ('deadline', _('Dead-line')),
            ('pk', _('ID')),
            ('client', _('Client')),
            ('created_on', _('Created on')),
            ('last_send', _('Last send')),
            ('last_action', _('Last action')),
        )

    def get_order_by(self, order_choice):
        if order_choice == 'default':
            return ['-deadline', 'status', '-last_send', '-last_action']
        return [order_choice]
carltongibson commented 9 years ago

👍

sehmaschine commented 9 years ago

why is it not possible to add multiple ordering values with the query string, similar to the OrderingFilter of DRF? See http://www.django-rest-framework.org/api-guide/filtering/#orderingfilter

Referring to the example above, I'd like to do:

?o=client,created_on
blueyed commented 8 years ago

@sehmaschine Just for reference: the new OrderingFilter in 0.15 should support this.

sehmaschine commented 8 years ago

@blueyed great, thanks.

soldovskii commented 3 years ago

I created OrderingFilterExtened for this purposes https://github.com/soldovskij/OrderingFilterExtened

Example:

class UserFilter(FilterSet):
    order_by = OrderingFilterExtened(
        fields_many=(
            ('full_name', ('first_name', 'last_name'), ('-first_name', '-last_name')),
        ),
        fields=(
            ('email', 'email'),  # model field name, parameter name
            ('last_login', 'last_login'),
            ('date_joined', 'date_joined'),
        ),
    )

class UserListAPIView(UsersPermissionQuerysetMixin, generics.ListAPIView):
    queryset = User.objects.all()
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = UserListSerializer
    pagination_class = LimitOffsetPagination
    filterset_class = UserFilter

Request example: api/v2/users/order_by=full_name or api/v2/users/order_by=-full_name

oxan commented 10 months ago

As the link above no longer works, another implementation of an extended OrderingFilter that supports ordering by multiple fields and also ordering by complex expressions:

class ExtendedOrderingFilter(OrderingFilter):
    def filter(self, qs, value):
        if value in EMPTY_VALUES:
            return qs

        ordering = []
        for param in value:
            fields = self.param_map[param.removeprefix('-')]
            if not isinstance(fields, tuple):
                fields = (fields, )
            for field in fields:
                if isinstance(field, str):
                    field = F(field)
                ordering.append(field.desc() if param.startswith('-') else field)

        return qs.order_by(*ordering)

Usage:


class UserFilter:
    sort = ExtendedOrderingFilter(fields=[
        ('email', 'email'),  # sort by regular field
        (Length('username'), 'username'),  # sort by expression
        (('first_name', 'last_name'), 'name'),   # sort by multiple fields
    ])
fidansamet commented 8 months ago

Thank you @oxan! Your fix has been helpful for me. Just one thing to note - when value includes "-" prefix, it considers both ordering values as descending. The same goes for ascending. So the direction of the ordering values depends on the param passed.

In my case, I have a fixed secondary ordering factor called "name". So when it orders by "email", I want to order it by "name" (ascending) as secondary ordering factor. Here is what worked for me:

class ExtendedOrderingFilter(filters.OrderingFilter):
    NAME_FIELD = "name"

    def filter(self, qs, value):
        if value in EMPTY_VALUES:
            return qs

        # If "name" is not primary ordering, order by it as secondary factor
        if self.NAME_FIELD not in value:
            value += [self.NAME_FIELD]

        return super().filter(qs, value)
class UserFilter(filters.FilterSet):
    ordering = ExtendedOrderingFilter(
        fields=(
            ("email", "email"),
            ("name", "name"),
        )
    )