Django аннотирует объект M2M полем из сквозной модели
Допустим, у меня есть следующие модели:
class Color(models.Model):
name = models.CharField(max_length=255, unique=True)
users = models.ManyToManyField(User, through="UserColor", related_name="colors")
class UserColor(models.Model):
class Meta:
unique_together = (("user", "color"), ("user", "rank"))
user = models.ForeignKey(User, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
rank = models.PositiveSmallIntegerField()
Я хочу получить всех пользователей из базы данных с их соответствующими цветами и рангами цветов. Я знаю, что могу сделать это, обратившись к сквозной модели, которая делает в общей сложности 3 обращения к БД:
users = User.objects.prefetch_related(
Prefetch(
"usercolor_set",
queryset=UserColor.objects.order_by("rank").prefetch_related(
Prefetch("color", queryset=Color.objects.only("name"))
),
)
)
for user in users:
for usercolor in user.usercolor_set.all():
print(user, usercolor.color.name, usercolor.rank)
Я обнаружил другой способ сделать это, аннотировав ранг на объекты Color
, что имеет смысл, поскольку у нас есть уникальное ограничение на пользователя и цвет.
users = User.objects.prefetch_related(
Prefetch(
"colors",
queryset=(
Color.objects.annotate(rank=F("usercolor__rank"))
.order_by("rank")
.distinct()
),
)
)
for user in users:
for color in user.colors.all():
print(user, color, color.rank)
Такой подход дает несколько преимуществ:
- Делает только 2 DB удара вместо 3.
- Не нужно иметь дело со сквозным объектом, что, на мой взгляд, более интуитивно понятно.
Однако, это работает только если я выстраиваю цепочку distinct()
(иначе я получаю дубликаты объектов), и я беспокоюсь, что это не совсем законный подход (возможно, я просто придумал хак, который может работать не во всех случаях).
Итак, законно ли второе решение? Есть ли лучший способ? Или мне лучше придерживаться первого?