Django Rest Ordering custom

I think a much easier approach to opalczynski's solution would be to introduce a new field for custom ordering:

from django import forms

import django_filters
from rest_framework import serializers

from .models import MyModel


class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        fields = ('field1',)


class CustomOrderingFilter(django_filters.FilterSet):
    order_by = django_filters.BooleanFilter(
        widget=forms.HiddenInput(),
        method='filter_order_by',
    )

    class Meta:
        model = MyModel
        fields = [
            'order_by'
        ]

    def filter_order_by(self, queryset, name, value):
        if value:
            return self.Meta.model.objects.filter(
                id__in=queryset.values_list('id', flat=True)
            ).order_by(value)

        return queryset

class TestViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    filter_class = CustomOrderingFilter

Then you can easily order by any field you want like this: example.com/api/mymodel/?order_by=partMissing.

In my example, I used a fixed model field, but you can change the way you order in the filter_order_by method on the CustomOrderingFilter. Just change it to the logic you want, but make sure to use the .filter(=queryset.values_list('id', flat=True)) to ensure that other filters that are set, are being used.


You can use FilterSet to annotate and then OrderingFilter to order accordingly. As an advantage you may use the OrderingField syntax and can still order by multiple fields ad the same time.

  • /api/?annotate_related={id}&order=subscribed
  • /api/?annotate_related={id}&order=-subscribed
  • /api/?annotate_related={id}&order=-subscribed,-modified

FilterSet:

class YourFilterSet(FilterSet):
    annotate_related = filters.NumberFilter(method="_annotate_related")

    class Meta:
        model = Model

    def _annotate_related(self, queryset, key, value, *args, **kwargs):
        # eg. annotate if user belongs to a certain category
        return queryset.annotate(is_subscribed=Case(When(annotate_related__id=value, then=1), output_field=IntegerField(), default=0))

ViewSet:

class YourViewSet(ModelViewSet):
    queryset = Model.objects.all()
    filterset_class = YourFilterSet
    filter_backends = [OrderingFilter, DjangoFilterBackend]
    ordering_fields = [
        "is_subscribed", # order by annotated field
    ]

I have very simple POC, that should allow you to implement more sophisticated solution.

views.py:

from rest_framework import viewsets

from ordering_test.models import Test
from ordering_test.ordering import MyCustomOrdering
from ordering_test.serializers import TestSerializer

class TestViewSet(viewsets.ModelViewSet):
    queryset = Test.objects.all()
    serializer_class = TestSerializer
    filter_backends = (MyCustomOrdering,)

ordering.py:

from rest_framework.filters import OrderingFilter

class MyCustomOrdering(OrderingFilter):

    allowed_custom_filters = ['testMethod']

    def get_ordering(self, request, queryset, view):
        """
        Ordering is set by a comma delimited ?ordering=... query parameter.

        The `ordering` query parameter can be overridden by setting
        the `ordering_param` value on the OrderingFilter or by
        specifying an `ORDERING_PARAM` value in the API settings.
        """
        params = request.query_params.get(self.ordering_param)

        if params:
            fields = [param.strip() for param in params.split(',')]
            # care with that - this will alow only custom ordering!
            ordering = [f for f in fields if f in self.allowed_custom_filters]
            if ordering:
                return ordering

        # No ordering was included, or all the ordering fields were invalid
        return self.get_default_ordering(view)

    def filter_queryset(self, request, queryset, view):

        ordering = self.get_ordering(request, queryset, view)
        if ordering:
            # implement a custom ordering here
            ordering = ['-id']

        if ordering:
            return queryset.order_by(*ordering)

        return queryset

The models.py and serializers.py are straightforward, but I will still include them here:

models.py:

from django.db import models

class Test(models.Model):
    test = models.CharField(max_length=120)
    test1 = models.CharField(max_length=120)

serializers.py:

from rest_framework import serializers

from ordering_test.models import Test

class TestSerializer(serializers.ModelSerializer):

    class Meta:
        model = Test
        fields = ('test', 'test1')

Happy coding!