Django REST Framework: Я хочу разрешить n+1 в SerializerMethodField

Я пытаюсь создать queryset, который возвращает Boolean из queryset, префетчированного с обратной ссылкой SerializerMethodField, как показано в приведенном ниже коде. Я создаю запрос, который определяет, существует ли объект для текущего пользователя, и возвращает булево значение. Однако, когда я использую предварительно выбранный набор запросов для фильтрации по текущему пользователю, как показано ниже, вместо предварительно выбранного набора запросов выдается новый набор запросов, и возникает проблема n+1.

В следующем коде, как мы можем сохранить кверисет и вернуть Booelan?

class VideoSerializer(serializers.ModelSerializer):
    is_viewed = serializers.SerializerMethodField()
    is_favorited = serializers.SerializerMethodField()
    is_wl = serializers.SerializerMethodField()

    class Meta:
        model = Video
        fields = (
            "pk",
            "is_viewed",
            "is_favorited",
            "is_wl",
        )

    @staticmethod
    def setup_eager_loading(queryset):
        queryset.prefetch_related('history_set', 'favorite_set')

    def get_is_viewed(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            try:
                obj.history_set.get(user=user) # <- here
                return True
            except History.DoesNotExist:
                pass
        return False

    def get_is_favorited(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            try:
                obj.favorite_set.get(user=user) # <- here
                return True
            except Favorite.DoesNotExist:
                pass
        return False

    def get_is_wl(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            try:
                Track.objects.get(playlist__user=user, playlist__is_wl=True, video=obj)
                return True
            except Track.DoesNotExist:
                pass
        return False

Было выдано большое количество наборов запросов.

Вы можете использовать Exists подзапрос.

Video.objects.annotate(is_favorite=Exists(Favorite.objects.filter(user=self.request.user, video=OuterRef("id"))))

После этого вы можете получить доступ к is_favorite атрибуту.

class VideoSerializer(serializers.ModelSerializer):
    is_viewed = serializers.SerializerMethodField()
    is_favorited = serializers.SerializerMethodField()
    is_wl = serializers.SerializerMethodField()

    class Meta:
        model = Video
        fields = (
            "pk",
            "is_viewed",
            "is_favorited",
            "is_wl",
        )

    @staticmethod
    def setup_eager_loading(queryset):
        queryset.prefetch_related('history_set', 'favorite_set')

    def get_is_viewed(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            try:
                obj.history_set.get(user=user) # <- here
                return True
            except History.DoesNotExist:
                pass
        return False

    def get_is_favorited(self, obj):
        return obj.is_favorite

    def get_is_wl(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            try:
                Track.objects.get(playlist__user=user, playlist__is_wl=True, video=obj)
                return True
            except Track.DoesNotExist:
                pass
        return False

Вы можете добавить аннотации для других полей (is_views, is_wl)

Предназначен другой запрос, выполняемый с вызовом метода prefetch_related. Вы можете найти его в документации django.

enter image description here

https://docs.djangoproject.com/en/3.2/ref/models/querysets/

Я думаю, что использование подзапроса и выражения запроса Exists является лучшим вариантом, как предложил 'Utkucan Bıyıklı'.

По обратной ссылке, как я понимаю, вы имеете в виду поле foreignkey, в таком случае вам нужно использовать select_related, prefetch_related используется для полей типа "многие-ко-многим". На основе этого вы можете использовать любой из приведенных ниже кодов. Убедитесь, что возвращает queryset.

 @staticmethod
 def setup_eager_loading(queryset):
     #prefetch_related for 'to-many' relationships
     queryset.prefetch_related('history_set', 'favorite_set')
     return quesyset


 @staticmethod
 def setup_eager_loading(queryset):
     #select_related for 'foreign key' relationships 
     queryset = queryset.select_related('history_set', 'favorite_set')
     return queryset
Вернуться на верх