'Django Rest Ordering custom

I'm using django rest framework and I would like to order with a custom method.

So, when I call this url for example : http://127.0.0.1:8000/api/sets/?ordering=partMissing

It's possible to call a custom method because I would like to order with the number of part missing by snippet. I made count the sum of number of part in the many to many field.



Solution 1:[1]

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.

Solution 2:[2]

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
    ]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Özer
Solution 2