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.
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