Получить количество строк одной модели, ссылающихся на объект другой модели в Django

Существует две модели: Articles и Views - Статьи и просмотры статей соответственно.

В модели Views есть поле article, ссылающееся через models.ForeignKey на объект модели Article. Мне необходимо получить список всех статей, отсортированный по количеству просмотров.

Пытался делать так:

Articles.objects.filter(status="published").order_by("views.count"):

но views.count не определяется. Ещё нужно сделать тоже самое, только для просмотров за последнюю неделю, то есть удовлетворяющих фильтру

view_datetime__gte=(datetime.datetime.now() + datetime.timedelta(days=-7)).strftime("%Y-%m-%d %H:%M:%S")

Как вариант решения я придумал создать двумерный список, в котором будут перебираться все статьи, отсортированные по чему угодно, каждой из которых сопоставляется её количество просмотров за всё время и за неделю, вроде такого:

[
[Article0, 78, 13]
[Article1, 96, 7]
[Article2, 48, 21]
[Article3, 456, 122]
]

Откуда отсортировать статьи по количеству просмотров проблем не составит, но есть стойкое ощущение, что возможна более простая реализация.


Ответы (1 шт):

Автор решения: onlikerop

Спасибо @Jack_oS за подсказку с агрегацией.

Для получения статей, отсортированных по количеству просмотров за всё время мы достаточно просто можем использовать обычный метод Count внутри annotate:

from django.db.models import Count
...

poparts = Articles.objects.annotate(views_num=Count('views__id')).order_by('-views_num')

В моём случае у каждого просмотра есть свой вес (это нужно для ручного добавления или убавления просмотров администраторами), поэтому код будет следующим:

from django.db.models import Sum
...

poparts = Articles.objects.annotate(views_num=Sum('views__view_weight')).order_by('-views_num')

Для сортировки по просмотрам, полученным исключительно за последнюю неделю, мы можем применить фильтр прямо к QuerySet'у объектов модели Articles, и получить вот такую вот штуку:

poparts = Articles.objects.filter(
        views__view_datetime__gte=(datetime.datetime.now() + datetime.timedelta(days=-7)).strftime("%Y-%m-%d %H:%M:%S")
    ).annotate(views_num=Sum('views__view_weight')).order_by('-views_num')
→ Ссылка