Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore null values in descending order using Django Rest Framework

I am using Django for my website, and hence decided to use Django Rest Framework for building my REST APIs. For a particular model, i want to filter on a text field (using SearchFilter for that), filter on a few categorical fields (FilterBackend with a FilterSet defined) and be able to order data based on some fields (OrderingFilter for this).

class StatsAPI(generics.ListAPIView):
    model = Stats
    queryset = Stats.objects.all()
    serializer_class = StatsSerializer
    filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter)
    filter_class = StatsFilter
    pagination_class = StatsPagination
    ordering_fields = ('__all__')
    search_fields = ('display_name')

The issue i am facing is with my ordering fields as they also contain nulls. Ordering in ascending order works fine. However ordering in descending order (www.example.com/api/stats/?ordering=-appearance), pushes the null values to the top.

How do i ignore the null values when using descending order? The number of fields on which ordering can be performed are roughly 20 in number.

like image 791
Shubham Avatar asked Mar 20 '17 09:03

Shubham


2 Answers

This is a slightly different solution -- rather than filtering null out, this replacement for filters.OrderingFilter just always makes sure they sort last:

class NullsAlwaysLastOrderingFilter(filters.OrderingFilter):
    """ Use Django 1.11 nulls_last feature to force nulls to bottom in all orderings. """
    def filter_queryset(self, request, queryset, view):
        ordering = self.get_ordering(request, queryset, view)

        if ordering:
            f_ordering = []
            for o in ordering:
                if not o:
                    continue
                if o[0] == '-':
                    f_ordering.append(F(o[1:]).desc(nulls_last=True))
                else:
                    f_ordering.append(F(o).asc(nulls_last=True))

            return queryset.order_by(*f_ordering)

        return queryset
like image 192
Scott Stafford Avatar answered Nov 18 '22 23:11

Scott Stafford


You can custom your own OrderingFilter:

# Created by [email protected] at 2022/8/13
from django.db.models import F, OrderBy
from django_filters import rest_framework as filters


class MyOrderingFilter(filters.OrderingFilter):
    def get_ordering_value(self, param):
        value = super().get_ordering_value(param)
        return OrderBy(F(value.lstrip("-")), descending=value.startswith("-"), nulls_last=True)

like image 1
BaiJiFeiLong Avatar answered Nov 18 '22 23:11

BaiJiFeiLong