N+1 запрос в SerializerMethodField
view
def get_queryset(self) -> QuerySet[Good]:
....
qs = (
Good.objects.values('brand_id', 'brand__name')
.annotate(
....
)
.prefetch_related(Prefetch('history', StocksHistory.objects.filter(Q(**subquery_filter_args))))
.order_by('-total_sales')
)
return qs
serializer
class ExtendedBrandSerializer(serializers.ModelSerializer):
...
history = serializers.SerializerMethodField()
class Meta:
model = Good
fields = (
...
'history',
)
def get_history(self, good: dict) -> dict:
....
return StocksHistorySerializer(
StocksHistory.objects.extra(select={'day': 'date( snap_at )'})
.values('day')
.filter(history_filter_query)
.annotate(
....
),
many=True,
).data
Relation: StocksHistory (*) -> (1) Good
.
В логах вижу N+1
запрос в SerializerMethodField
. Как можно пофиксить?
Возможно, есть вариант вынести annotate
с сериалайзера во вьюху?
Суть в том, что в ответе мне нужен ключ history
, в котором будет список объектов "дочерних" элементов?
Второй вариант, который должен сработать, если не получается сделать одним запросом.
Указываете класс, определенный ниже, в filter_backends
вашей вьюхи, он должен стоять в самом конце.
from rest_framework.filters import BaseFilterBackend
class HistoryFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
"""
Return a filtered queryset.
"""
secondary_queryset = queryset.model.history.all().filter(id__in=queryset.values_list('id', flat=True))
secondary_data_dict = {x.good_id: x for x in secondary_queryset} # Под вопросом, требуются правки, информация
new_queryset = []
for x in queryset:
# <param_name> - название поля, в котором будет храниться доп инфа
if x.id in secondary_data_dict:
setattr(x, '<param_name>', secondary_data_dict[x.id])
else:
setattr(x, '<param_name>', None)
new_queryset.append(x)
return new_queryset
Правда вероятно, сломается пагинация, но думаю это легко решить, создав фейковый queryset. Пока ответить про qs не могу, надо подумать. Дополню при появлении доп информации
Тогда сериализатор должен будет выглядеть следущим образом
class ....(...):
history = serializer.SerializerMethodField()
def get_history(self, obj):
"""
obj - это что поместили в <param_name>. Если поместили словарь - будет словарь.
"""
# что то делаем
return result # любое словарь/значение/данные сериализатора и тд