Django: почему annotate добавляет элементы в мой кверисет?

Я пытаюсь сделать что-то простое. У меня есть объекты Item, и пользователи могут отмечать их как избранные. Так как эти объекты не принадлежат пользователю, я решил использовать ManyToMany между User и Item для записи отношения избранности. Если пользователь находится в поле favoriters элемента, это означает, что пользователь отметил его как избранный.

Затем, когда я получаю объекты для определенного пользователя, я хочу аннотировать каждый элемент, чтобы указать, является ли он избранным пользователем. Для этого я создал метод add_is_favorite_for().

Вот (упрощенный) код:

class ItemQuerySet(query.QuerySet):
    def add_is_favorite_for(self, user):
        """add a boolean to know if the item is favorited by the given user"""
        condition = Q(favoriters=user)
        return self.annotate(is_favorite=ExpressionWrapper(condition, output_field=BooleanField()))

class Item(models.Model):
    objects = Manager.from_queryset(ItemQuerySet)()

    favoriters = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)

Это не работает так, как ожидалось, кажется, что Django добавляет элемент для каждого пользователя, который отметил элемент. Это приводит к таким безумным вещам, как:

Item.objects.count() # 10
Item.objects.add_is_favorite_for(some_user).count() # 16 -> how the hell an annotation can return more results than initial queryset?

Я что-то упускаю...

Вы можете изменить условие в аннотации, чтобы использовать подзапрос, получить все элементы, которые пользователь выбрал, а затем проверить, есть ли id элемента в подзапросе.

Это должно устранить вашу проблему с дублированием:

def add_is_favorite_for(self, user):
    return self.annotate(is_favorite=ExpressionWrapper(
        Q(id__in=Item.objects.filter(favoriters=user).values('id')),
        output_field=BooleanField()
    ))
Вернуться на верх