'DRF and django_filters. Change queryset before applying filters

There is a ModelViewSet and a FilterSet that work great. But the problem is that I need to transform the queryset before filtering, for this I overridden the get_queryset() method. It changes the queryset, but as a result, on the page with a list of objects, I see that no changes have occurred.

If I override the list() method using the rewritten get_queryset() method in it:

queryset = self.get_queryset()

Changes will occur, but filters will not work on this queryset. I tried using:

qs = self.filter_queryset(self.get_queryset())

in this case, the filters work, but the data on the page remains as it was before the change in get_queryset(). Please tell me what is my mistake, why can't I filter the queryset with changes and what should be done to make the filtering of the transformed queryset possible?

EDIT:

views.py

from .serializers import OfferSerializer
from rest_framework import viewsets
from rest_framework.response import Response
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Offer


def validate(price:str, term:str, deposit:str) -> bool:
    """ Validating type of parameters. """
    try:
        price, term, deposit = int(price), int(term), int(deposit)
        return True
    except:
        return False

def calculate_payment(qs, price:str, term:str, deposit:str):
    """ Calculating payment by entry parameters and bank rate. """
    clear_payment = (int(price) - int(deposit)) / (int(term) * 12)
    for el in qs:
        rate_multiplier = (el.rate + 100) / 100
        el.payment = clear_payment * rate_multiplier
    

class OfferFilter(filters.FilterSet):
    payment_min = filters.NumberFilter(field_name="payment", lookup_expr='gte')
    payment_max = filters.NumberFilter(field_name="payment", lookup_expr='lte')
    rate_min = filters.NumberFilter(field_name="rate", lookup_expr='gte')
    rate_max = filters.NumberFilter(field_name="rate", lookup_expr='lte')
    bank_name = filters.CharFilter(field_name="bank_name", lookup_expr='contains')

    order_by = filters.OrderingFilter(
        fields=(
            ('payment', 'payment'),
            ('bank_name', 'bank_name'),
            ('rate', 'rate'),
        ),
    )
    class Meta:
        model = Offer
        fields = ('payment_min', 'payment_max', 'rate_min', 'rate_max', 'bank_name')
    

class OfferViewSet(viewsets.ModelViewSet):
    serializer_class = OfferSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = OfferFilter
    filterset_fields = ['rate', 'payment', 'bank_name']

    def get_queryset(self):
            queryset = Offer.objects.all()
            query_params = dict(self.request.GET.items())
            if 'price' in query_params and 'term' in query_params:
                deposit = query_params['deposit'] if 'deposit' in query_params else 0
                price = query_params['price']
                term = query_params['term']
                if validate(price, term, deposit):
                    calculate_payment(queryset, price, term, deposit)
            return queryset

    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

    def list (self, request):
        queryset = self.get_queryset()
        queryset = self.filter_queryset(queryset)
        serializer = OfferSerializer(queryset, many=True)
        return Response(serializer.data)


Solution 1:[1]

You need to annotate the queryset instead of iterating over it and setting attributes

from django.db.models import F, Value

...

    def get_queryset(self):
        queryset = Offer.objects.all()
        query_params = dict(self.request.GET.items())
        if 'price' in query_params and 'term' in query_params:
            deposit = query_params['deposit'] if 'deposit' in query_params else 0
            price = query_params['price']
            term = query_params['term']
            if validate(price, term, deposit):
                clear_payment = (int(price) - int(deposit)) / (int(term) * 12)
                queryset = queryset.annotate(
                    rate_multiplier=(F('rate') + Value(100.0)) / Value(100.0)
                ).annotate(
                    payment=Value('clear_payment') * F('rate_multiplier')
                )
            return queryset

If Django cannot work out the correct return type from the annotations you may need to wrap them in ExpressionWrapper to specify the return type

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 Iain Shelvington