DRF и django_filters. Изменение набора запросов перед применением фильтров
Есть ModelViewSet и FilterSet, которые отлично работают. Но проблема в том, что мне нужно преобразовать queryset перед фильтрацией, для этого я переопределил метод get_queryset(). Он изменяет queryset, но в результате на странице со списком объектов я вижу, что никаких изменений не произошло.
Если я переопределю метод list(), используя в нем переписанный метод get_queryset():
queryset = self.get_queryset()
Изменения произойдут, но фильтры не будут работать на этом наборе запросов. Я пробовал использовать:
qs = self.filter_queryset(self.get_queryset())
в этом случае фильтры работают, но данные на странице остаются такими, какими они были до изменения в get_queryset(). Подскажите, пожалуйста, в чем моя ошибка, почему я не могу фильтровать кверисет с изменениями и что нужно сделать, чтобы фильтрация преобразованного кверисета стала возможной?
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)
Вам нужно аннотировать набор запросов вместо того, чтобы итерировать его и устанавливать атрибуты
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
Если Django не может определить правильный тип возврата из аннотаций, вам может потребоваться обернуть их в ExpressionWrapper для указания типа возврата