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

и сериализатор

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

Отношение: StocksHistory (*) -> (1) Good.

У меня есть N+1 запросов в SerializerMethodField. Как я могу это исправить?

Возможно, есть способ перенести аннотацию из сериализатора в представление?

В итоге мне также нужен ключ history в ответе, который будет содержать список этих дочерних объектов.

Вы можете переместить все ваши фильтры и аннотации на метод get_queryset:

    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)
                    ).extra(
                        select={'day': 'date( snap_at )'}
                    ).annotate(
                        # ...
                    )
                )
            ).order_by('-total_sales')
        )
        return qs

Тогда просто используйте good.history.all() в вашем методе сериализатора, чтобы избежать N+1, вот так:

class ExtendedBrandSerializer(serializers.ModelSerializer):
    # ...
    history = serializers.SerializerMethodField()

    class Meta:
        model = Good
        fields = (
            # ...
            'history',
        )

    def get_history(self, good: dict) -> dict:
        # ...
        return StocksHistorySerializer(
            good.history.all(),
            many=True,
        ).data
Вернуться на верх