Итак, мы продолжаем работать над нашим общего проекта — веб-приложения для управления библиотекой. На данном этапе у нас есть несколько моделей: Book, Author и Publisher. Теперь мы будем использовать агрегации, аннотации, Q- и F-объекты для выполнения различных аналитических запросов.
Вот наша текущая модель базы данных:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
birth_date = models.DateField(null=True, blank=True)
def __str__(self):
return self.name
class Publisher(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=50)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name='books')
price = models.DecimalField(max_digits=10, decimal_places=2)
publication_date = models.DateField()
def __str__(self):
return self.title
Задача 1: общая стоимость книг
Давайте начнем с простого: нам нужно узнать, какова общая стоимость всех книг в нашей библиотеке. Конечно же, мы будем использовать функцию aggregate().
from django.db.models import Sum
total_price = Book.objects.aggregate(total=Sum('price'))
print(total_price)
Что тут происходит?
aggregate(Sum('price')): возвращает словарь, где ключом будет имя, которое мы указали для результата (total), а значением — общая сумма поляpriceу всех записей в таблицеBook.
Вывод будет выглядеть так:
{'total': Decimal('10753.50')}
--
Задача 2: средняя цена книг для каждого издателя
Теперь нам нужно вычислить среднюю стоимость книг, выпущенных каждым издателем. Здесь нам поможет функция annotate().
from django.db.models import Avg
publishers = Publisher.objects.annotate(avg_price=Avg('books__price'))
for publisher in publishers:
print(f"{publisher.name}: {publisher.avg_price}")
Разбор:
annotate(avg_price=Avg('books__price')): добавляет вычисляемое полеavg_priceк каждому объектуPublisher. Это поле содержит среднюю цену всех книг, связанных с этим издателем.
Пример вывода:
O'Reilly Media: 25.50
Penguin Books: 18.75
Random House: 20.00
Задача 3: количество книг, выпущенных после 2020 года
Теперь мы используем фильтрацию и аннотацию, чтобы подсчитать количество книг, выпущенных каждым издателем после 2020 года.
from django.db.models import Count
publishers = Publisher.objects.annotate(
books_after_2020=Count('books', filter=models.Q(books__publication_date__year__gt=2020))
)
for publisher in publishers:
print(f"{publisher.name}: {publisher.books_after_2020} книг после 2020 года")
Что нового?
- Новый параметр
filterв функции аннотации позволяет применить фильтрацию только для аннотируемых данных. Q(books__publication_date__year__gt=2020): фильтрует книги с годом публикации больше 2020.
Задача 4: найти автора с максимальным количеством книг
Как насчет того, чтобы узнать, кто из авторов написал больше всего книг? Для этого подойдет функция annotate с Count.
author = Author.objects.annotate(book_count=Count('books')).order_by('-book_count').first()
print(f"Самый продуктивный автор: {author.name}, {author.book_count} книг.")
Count('books'): считает количество связанных книг для каждого автора..order_by('-book_count'): сортирует авторов по количеству книг в порядке убывания..first(): возвращает первого автора из отсортированного списка.
Задача 5: сравнение цен на книги одного издателя
Теперь сложнее: как сравнить цену определенной книги со средней ценой других книг того же издателя? Для этого пригодятся F-объекты.
from django.db.models import F
books = Book.objects.annotate(
publisher_avg_price=Avg('publisher__books__price')
).filter(price__lt=F('publisher_avg_price'))
for book in books:
print(f"{book.title} дешевле средней цены у издателя {book.publisher.name}.")
Новый элемент:
F('publisher_avg_price'): позволяет работать со значением вычисляемого поля прямо в запросе.
Задача 6: сложный запрос с Q-объектами
Давайте найдем всех авторов, у которых есть хотя бы одна книга дешевле 10 долларов или стоимость книги превышает 100 долларов у издателя из США.
q_authors = Author.objects.filter(
Q(books__price__lt=10) | Q(books__price__gt=100, books__publisher__country='USA')
).distinct()
for author in q_authors:
print(f"Автор: {author.name}")
Разбор:
Q(): позволяет объединить условия с помощью логического ИЛИ..distinct(): убирает дублирующиеся записи, если автор подходит под несколько условий сразу.
Задача 7: количество книг по году
Для дополнительного челенджа давайте подсчитаем количество книг, опубликованных в каждый год.
from django.db.models.functions import TruncYear
yearly_books = Book.objects.annotate(year=TruncYear('publication_date')).values('year').annotate(count=Count('id')).order_by('year')
for entry in yearly_books:
print(f"Год: {entry['year']}, Количество книг: {entry['count']}")
Пояснения:
TruncYear('publication_date'): позволяет извлечь год из даты публикации..values('year'): группирует результаты по году..annotate(count=Count('id')): считает количество книг для каждого года.
Итог
Вот и всё! Мы использовали агрегации, аннотации, Q- и F-объекты для решения реальных задач, касающихся нашего проекта. Все примеры, которые мы рассмотрели, могут быть легко адаптированы для решения ваших собственных задач в веб-разработке.
Если вы чувствуете себя немного перегруженными, это нормально. Возвращайтесь к этой лекции при необходимости и постепенно экспериментируйте с разными подходами. Помните, как говорил один мудрый разработчик: "Наилучший способ узнать что-то новое — это закоммитить это в продакшен... Но лучше всё-таки в локальной копии!" 😊
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ