How to use DRF OrderingFilter inside @action view?

Viewed 917

I can't find information on how to use OrderingFilter from django rest framework inside ModelViewSet @action, does anyone know how would i implement that?

I have categories and those categories have products that i get with that action, i want to order those products with a query string. Basically i would need to know how to use it on a queryset, but i cant find any info about that.

Example url: http://example.com/categories/2/get_products/?ordering=price

And here is the code, thanks for any help.

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

    @action(detail=True)
    def get_products(self, request, pk=None):
        categories = Category.objects.get(id=pk).get_descendants(
            include_self=True).values_list('product_type__id', flat=True)

        products = Product.objects.filter(
            product_type__id__in=list(categories)
        )
        paginator = ProductPagination()
        paginated_products = paginator.paginate_queryset(products, request)
        serializer = ProductSerializer(
            paginated_products, many=True, context={'request': request})
        return paginator.get_paginated_response(serializer.data)
2 Answers

I would suggest creating a different api view for products:

class CategoryProductViewSet(viewsets.ModelViewSet):
    serializer_class = ProductSerializer
    filter_backends = (filters.OrderingFilter,)
    ordering_fields = ('price', )
 
    def get_queryset(self):
        categories = Category.objects.get(id=self.kwargs['category_pk'])\
           .get_descendants(include_self=True)\
           .values_list('product_type__id', flat=True)

        return Product.objects.filter(product_type__id__in=categories)

And then in your routers add the url:

router.register(r'categories/(?P<category_pk>[0-9]+)/products', CategoryProductViewSet)

Going with this approach will let you benefit from everything that a normal viewset can offer.

I was having the same problem and I wasn't happy with creating other view just for an action which fit perfectly inside the ViewSet, so I checked DRF code and it turns out that you just need to add one line:

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    filter_backends = [filters.OrderingFilter]

    @action(detail=True)
    def get_products(self, request, pk=None):
        qs = self.filter_queryset(self.get_queryset())
        # rest of code inside function would use the queryset qs from above.

DRF handles this inside GenericAPIView. If you are using ListModelMixin, as in a ModelViewSet, you can also check the list method for serializing and pagination, cf.:

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

EDIT: wrong links.

Related