Курс по Django: https://stepik.org/a/183363
Архив проекта: 35_sitewomen.zip
Продолжаем
изучение ORM Django. Часто вызов
агрегирующих функций применяется не ко всем записям, а к группам,
сформированным по определенному полю. Например, в таблице Women можно
сгруппировать записи по cat_id и получим две
независимые группы записей. Затем, к каждой группе применить агрегацию и
получить искомые значения.
В качестве
примера, давайте подсчитаем число постов для каждой группы категорий. Для этого запишем такую команду:
Women.objects.values("cat_id").annotate(Count("id"))
Ее действие
графически можно представить так:
То есть, здесь
группировка автоматически выполняется по единственному полю, которое мы
выбираем из таблицы. При необходимости можем изменить имя параметра id__count, скажем, на total, указав его
явно в методе annotate():
Women.objects.values('cat_id').annotate(total=Count('id'))
Вот так делается
группировка записей с помощью метода values() и вызов
агрегирующих функций через метод annotate().
Мало того,
далее, мы можем прописывать другие методы, после метода annotate(). Например вот
так делается отбор рубрик, у которых число постов больше нуля:
lst = Category.objects.annotate(total=Count("posts")).filter(total__gt=0)
Из SQL-запроса видно,
что в этом случае происходит группировка по всем полям таблицы, поэтому ее как
бы и нет (хотя бы потому, что поле id уникально).
Отобразим
результаты в консоли:
for i, x in enumerate(lst):
if i == 0:
print(list(x.__dict__)[1:])
print(list(x.__dict__.values())[1:])
Получим:
['id',
'name', 'slug', 'total']
[1,
'Актрисы', 'aktrisy', 5]
[2, 'Певицы',
'pevicy', 5]
По аналогии мы можем
отобрать все теги из таблицы tagpost, которым соответствует хотя бы
одна статья:
lst = TagPost.objects.annotate(total=Count("tags")).filter(total__gt=0)
Надо сказать,
что подобные запросы получаются не самые быстрые, поэтому перед их применением
нужно оценить необходимость получения таких составных данных непосредственно из
СУБД. Возможно, проще получить список данных, а затем, через цикл провести их
дополнительную обработку.
Давайте
воспользуемся этим запросом для вывода только значащих тегов. Для этого
перейдем в файл women_tags.py и перепишем
функцию show_all_tags() следующим образом:
@register.inclusion_tag('women/list_tags.html')
def show_all_tags():
return {"tags": TagPost.objects.annotate(total=Count("tags")).filter(total__gt=0)}
Запускаем
тестовый веб-сервер и у нас теперь отображается только два тега. Но
присутствуют три рубрики, одна из которых («Спортсменки») пустая. Давайте для
вывода рубрик сделаем то же самое:
@register.inclusion_tag('women/list_categories.html')
def show_categories(cat_selected_id=0):
cats = Category.objects.annotate(total=Count("posts")).filter(total__gt=0)
return {"cats": cats, "cat_selected": cat_selected_id}
Теперь видим
только заполненные рубрики. Вот пример того, как можно использовать такие
сложные запросы с небольшим набором данных в таблицах.
Вычисления на стороне СУБД
В заключение
этого занятия отмечу, что фреймворк Django содержит набор
функций, позволяющие выполнять вычисления, связанные с полями таблицы, на
стороне СУБД. Полный их список можно посмотреть по ссылке:
https://docs.djangoproject.com/en/4.2/ref/models/database-functions/
Фактически,
здесь приведены обертки над функциями, которые выполняются в СУБД. Этих функций
достаточно много. Это и функции работы со строками, датой, математические
функции и так далее. Использование этих функций является рекомендуемой
практикой, т.к. СУБД оптимизировано для их выполнения. Конечно, все имеет свои
разумные пределы и нужно лишь по необходимости прибегать к этому функционалу.
Давайте для
примера рассмотрим использование функции Length для вычисления
длины строки. Первым делом нам нужно ее импортировать:
from django.db.models.functions import Length
И, затем,
аннотируем новое вычисляемое поле, например, для имен из таблицы husband:
lst = Husband.objects.annotate(len_name=Length('name'))
В результате,
наряду со всеми стандартными полями, получим дополнительное поле len_name:
['id', 'name', 'age', 'm_count',
'len_name']
[1,
'Брэд Питт', 30, 4, 9]
[2,
'Том Акерли', 31, 1, 10]
[3,
'Дэниэл Модер', 54, 0, 12]
[4, 'Кук Марони', 37, 1, 10]
[5,
'Сергей Балакирев', 101, 0, 16]
По аналогии
используются все остальные подобные функции.
Курс по Django: https://stepik.org/a/183363