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