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)
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