Как применить произвольный фильтр к определенной цепочке prefetch_related() в Django?
Я пытаюсь оптимизировать уволенные запросы API. У меня есть четыре модели, а именно User, Content, Rating и UserRating с некоторыми отношениями друг к другу. Я хочу, чтобы соответствующий API возвращал все существующее содержимое вместе с количеством его оценок, а также оценку, данную конкретным пользователем этому содержимому.
Раньше я делал нечто подобное: Content.objects.all() в качестве набора запросов, но я понял, что в случае огромного количества данных будут запущены тонны запросов. Поэтому я приложил некоторые усилия для оптимизации запросов, используя select_related и prefetch_related. Однако я имею дело с дополнительным поиском в python, который я надеюсь устранить с помощью управляемого prefetch_related() - применения фильтра только для определенного prefetch во вложенных prefetch и select.
Вот мои модели:
from django.db import models
from django.conf import settings
class Content(models.Model):
title = models.CharField(max_length=50)
class Rating(models.Model):
count = models.PositiveBigIntegerField(default=0)
content = models.OneToOneField(Content, on_delete=models.CASCADE)
class UserRating(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE
)
score = models.PositiveSmallIntegerField()
rating = models.ForeignKey(
Rating, related_name="user_ratings", on_delete=models.CASCADE
)
class Meta:
unique_together = ["user", "rating"]
Вот что я сделал на данный момент:
contents = (
Content.objects.select_related("rating")
.prefetch_related("rating__user_ratings")
.prefetch_related("rating__user_ratings__user")
)
for c in contents: # serializer like
user_rating = c.rating.user_ratings.all()
for u in user_rating: # how to remove this dummy search?
if u.user_id == 1:
print(u.score)
Запросы:
(1) SELECT "bitpin_content"."id", "bitpin_content"."title", "bitpin_rating"."id", "bitpin_rating"."count", "bitpin_rating"."content_id" FROM "bitpin_content" LEFT OUTER JOIN "bitpin_rating" ON ("bitpin_content"."id" = "bitpin_rating"."content_id"); args=(); alias=default
(2) SELECT "bitpin_userrating"."id", "bitpin_userrating"."user_id", "bitpin_userrating"."score", "bitpin_userrating"."rating_id" FROM "bitpin_userrating" WHERE "bitpin_userrating"."rating_id" IN (1, 2); args=(1, 2); alias=default
(3) SELECT "users_user"."id", "users_user"."password", "users_user"."last_login", "users_user"."is_superuser", "users_user"."first_name", "users_user"."last_name", "users_user"."email", "users_user"."is_staff", "users_user"."is_active", "users_user"."date_joined", "users_user"."user_name" FROM "users_user" WHERE "users_user"."id" IN (1, 4); args=(1, 4); alias=default
Как вы можете видеть на приведенных выше уволенных запросах, я сделал только три запроса, а не слишком много запросов в прошлом. Однако, я полагаю, что могу убрать поиск в python (вторая итерация for), используя вместо этого фильтр на моем последнем запросе users_user"."id" IN (1,). Согласно этому посту и моим усилиям, я не смог применить .filter(rating__user_ratings__user_id=1) на третьем запросе. На самом деле, я не смог решить свою проблему, используя Prefetch(..., queryset=...) пример, приведенный в этом ответе .
Я думаю, что вы ищете объект Prefetch: https://docs.djangoproject.com/en/4.0/ref/models/querysets/#prefetch-objects
Попробуйте это:
from django.db.models import Prefetch
contents = (
Content.objects.select_related("rating")
.prefetch_related(Prefetch(
"rating__user_ratings",
queryset=UserRating.objects.filter(user__id=1),
to_attr="user_ratings"
))
for c in contents: # serializer like
user_rating = c.rating.user_ratings.all() <-- Should contains only user=1 ratings
for u in user_rating:
print(u.score)