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  # любое словарь/значение/данные сериализатора и тд

Вернуться на верх