Django REST Framework:我想在 SerializerMethodField 中解析 n+1

Django REST Framework: I want to resolve n+1 in SerializerMethodField

我正在尝试创建一个 returns 布尔值的查询集,该查询集来自 SerializerMethodField 预取的反向引用,如下面的代码所示。 我正在创建一个确定当前用户和 returns 布尔值是否存在对象的对象。 但是,当我使用预取的查询集按当前用户进行过滤时,如下所示,发出了一个新的查询集而不是预取的查询集,出现了n+1问题。

在下面的代码中,我们如何保存queryset和return 布尔兰?

# serializers.py

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

A large number of query sets were issued.

#models.py

class Video(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField("title", max_length=300)

    def __str__(self):
        return self.title

class History(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE
    )
    video = models.ForeignKey(Video, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.user}, {self.video.title}"

class Favorite(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE
    )
    video = models.ForeignKey(Video, on_delete=models.CASCADE)
    created_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"{self.user}, {self.video.title}"

您可以使用 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 文档中找到它。

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

我认为使用子查询和 Exists 查询表达式是更好的选择,正如 'Utkucan Bıyıklı' 建议的那样。

通过反向引用,我知道你指的是一个外键字段,在这种情况下你需要使用select_relatedprefetch_related用于许多字段。 基于它,您可以使用以下任何代码。确保 return 查询集。

 @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