Получение количества столбцов, различающихся по двум столбцам

Вот упрощенное представление моих моделей:

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    template_id = models.IntegerField(null=True)
    ...

Я хочу показать количество раз, когда шаблон был использован пользователями. Поэтому, когда я перечисляю шаблоны, я хочу иметь возможность сказать Used by X users. Основная проблема заключается в том, что я не хочу учитывать пользователя только один раз (поэтому если пользователь использует шаблон дважды, он все равно считается "одним случаем использования"). Все посты на stackoverflow говорят о том, чтобы сделать что-то вроде этого:

counts = Post.objects.all().values("template_id").order_by().annotate(count=Count("template_id"))

Но это явно удваивает количество пользователей, которые используют один и тот же шаблон дважды. Я смог сделать отчетливый подсчет пар template_id и user следующим образом:

Post.objects.all().values("template_id", "user__id").distinct()
# Printing this out, I get 2 distinct entries in the QuerySet:
# <QuerySet [{'template_id': 1, 'user__id': 1}, {'template_id': 1, 'user__id': 2}]>

Однако, когда я пытаюсь получить подсчеты template_id (код ниже), кажется, что он игнорирует distinct и по-прежнему дважды подсчитывает пользователей.

Post.objects.all().values("template_id", "user__id").distinct().values("template_id").annotate(count=Count("template_id"))
# Printing this out I get `count` = 3, which double counts a user.
# <QuerySet [{'template_id': 1, 'count': 3}]>

Для того, что это стоит, я написал быстрый тестовый пример, который и дает сбой.

user1 = baker.make("User")
user2 = baker.make("User")

# Populate posts
quest1 = baker.make("post.Post", user=user1, template_id=1)
quest2 = baker.make("post.Post", user=user1, template_id=1)  # Duplicate shouldn't count
quest3 = baker.make("post.Post", user=user2, template_id=1)

Почему вы просто не используете:

counts = Post.objects.all().values("template_id", "user__id").distinct().values("template_id").count()

Заставил его работать, используя встроенный в Django ORM, сделав следующее:

        template_ids = []  # My templates

        # Get the number of times each template_id was used.
        top_template_counts = (
            Post.objects.filter(template_id__in=template_ids)
            .values("template_id")  # groups by template ids
            .annotate(user_count=Count("user", distinct=True))  # Gets the number of users using each template
            .order_by("-user_count")
        )

        # Accessing `top_template_counts` 
        for template_id_count in top_template_counts[:10]:
            template_id = template_id_count["template_id"]
            count = template_id_count["parent_count"]
Вернуться на верх