📖 Теория — Урок 26
⚡ Ключевые концепции урока
- aggregate() — одно значение для всего QuerySet (словарь)
- annotate() — вычисленное поле для каждого объекта
- order_by() — сортировка;
'-field'— убывание - Срезы —
QuerySet[0:5]→LIMIT 5 OFFSET 0 - Subquery + OuterRef — встроенный подзапрос
- ExpressionWrapper — арифметика с явным типом результата
- DRF — надстройка над Django для создания REST API
Часть 1: Продвинутые ORM-запросы
1. Агрегация данных
Агрегация позволяет выполнять статистические операции над набором данных: подсчёт количества, сумма, среднее, минимум, максимум. В Django ORM для этого используются два метода: aggregate() и annotate().
Основные агрегационные функции
| Функция | SQL-эквивалент | Что делает |
|---|---|---|
Count('field') | COUNT(field) | Количество записей |
Sum('field') | SUM(field) | Сумма значений |
Avg('field') | AVG(field) | Среднее значение |
Min('field') | MIN(field) | Минимальное значение |
Max('field') | MAX(field) | Максимальное значение |
Импорт: from django.db.models import Count, Sum, Avg, Min, Max
Метод aggregate()
Возвращает словарь с вычисленными значениями для всего QuerySet. Используется когда нужно одно итоговое значение по всей таблице.
from django.db.models import Avg, Count
# Общее количество книг и средняя цена
aggregates = Book.objects.aggregate(
total_books=Count('id'),
average_price=Avg('price')
)
print(aggregates['total_books']) # например: 42
print(aggregates['average_price']) # например: 19.99
Метод annotate()
Добавляет вычисленное поле к каждому объекту QuerySet. Используется совместно с values() для группировки — аналог GROUP BY в SQL.
from django.db.models import Count
# Количество книг каждого автора
author_book_count = Book.objects.values('author').annotate(
book_count=Count('id')
)
for entry in author_book_count:
print(f"Автор: {entry['author']}, Книг: {entry['book_count']}")
aggregate()→{'total': 42}— один словарь для всего QuerySetannotate()→ каждый объект получает дополнительный атрибут
Комбинирование annotate() с функциями ExtractYear/ExtractQuarter
Функции Extract* позволяют извлекать части даты и группировать по ним:
from django.db.models import Count, Avg
from django.db.models.functions import ExtractYear, ExtractQuarter
# Количество книг по годам
books_per_year = Book.objects.annotate(
year=ExtractYear('published_date')
).values('year').annotate(book_count=Count('id'))
# Средняя цена по кварталам
avg_price_per_quarter = Book.objects.annotate(
quarter=ExtractQuarter('published_date')
).values('quarter').annotate(average_price=Avg('price'))
2. Метод order_by()
Используется для сортировки объектов QuerySet. Синтаксис: QuerySet.order_by(*fields).
- По умолчанию — сортировка по возрастанию
- Поля с
-перед именем — сортировка по убыванию - Можно сортировать по полям связанных моделей через
__
# По одному полю
books = Book.objects.order_by('title') # ASC
books = Book.objects.order_by('-published_date') # DESC
# По нескольким полям
books = Book.objects.order_by('author__name', 'title')
# По полю связанной таблицы (Publisher)
books = Book.objects.filter(
publisher__isnull=False
).order_by('publisher__name')
3. Ограничение количества объектов (срезы)
Django не имеет метода limit(). Вместо этого используется Python-синтаксис срезов. Срез транслируется в LIMIT / OFFSET в SQL.
QuerySet[start:stop]
start— индекс первого объекта (по умолчанию 0)stop— индекс первого объекта, который не включаетсяstep— шаг (обычно не используется)
# Первые 3 книги
books = Book.objects.all()[:3]
# Книги с 4-й по 6-ю (skip 3, take 3)
books = Book.objects.all()[3:6]
# Первые 5 по цене
books = Book.objects.order_by('price')[:5]
# Последние 5 по дате публикации
books = Book.objects.order_by('-published_date')[:5]
filter(), order_by() и других методов. Нельзя делать срез, а потом фильтровать.
4. Подзапросы (Subquery и OuterRef)
Подзапросы позволяют включать результаты одного QuerySet в другой запрос. Полезны для сложной фильтрации и аннотации на основе связанных данных.
Subquery— оборачивает внутренний QuerySet, делая его подзапросомOuterRef— ссылается на поле из внешнего (главного) запроса
Импорт: from django.db.models import OuterRef, Subquery
Когда НЕ нужен Subquery
Если aggregate() возвращает одно значение — его можно использовать в фильтре напрямую, без Subquery и OuterRef:
from django.db.models import Avg
# Просто вычисляем среднюю цену и фильтруем
average_price = Book.objects.aggregate(avg_price=Avg('price'))['avg_price']
books_below_avg = Book.objects.filter(price__lt=average_price)
Когда нужен Subquery + OuterRef
Когда подзапрос должен ссылаться на поле из каждой строки внешнего запроса (коррелированный подзапрос):
from django.db.models import OuterRef, Subquery, Min
# Аннотируем каждую книгу минимальной ценой среди книг того же автора
subquery = Book.objects.filter(
author=OuterRef('author') # OuterRef ссылается на author текущей книги
).values('author').annotate(
min_price=Min('price')
).values('min_price')
books = Book.objects.annotate(
min_author_price=Subquery(subquery)
)
for book in books:
print(book.title, book.price, book.min_author_price)
Применения подзапросов
- Фильтрация: отфильтровать строки на основе вычисленного значения связанных данных
- Аннотация: добавить вычисленное поле из другой таблицы к каждому объекту
- Агрегация: комбинировать с агрегатными функциями для сложных вычислений
5. ExpressionWrapper
ExpressionWrapper используется для оборачивания сложных арифметических выражений, которые не могут быть обработаны напрямую. Обязательно требует явного указания типа результата через output_field.
ExpressionWrapper(expression, output_field=поле_типа)
expression— арифметическое выражение (сF()-объектами)output_field— тип результата:fields.FloatField(),fields.DecimalField(), и т.д.
from django.db.models import F, ExpressionWrapper, fields
# Процент скидки: (1 - discounted_price / price) * 100
books = Book.objects.annotate(
discount_pct=ExpressionWrapper(
(1 - F('discounted_price') / F('price')) * 100,
output_field=fields.FloatField()
)
)
for book in books:
print(f"{book.title}: скидка {book.discount_pct:.1f}%")
output_field Django не знает, какой тип данных ожидать — запрос упадёт с FieldError.
6. Временные метки
Django предоставляет модуль django.utils.timezone для работы с временными зонами и фильтрации по временным меткам.
from django.utils import timezone
# Книги, опубликованные за последние 2 года
two_years_ago = timezone.now() - timezone.timedelta(days=365 * 2)
books = Book.objects.filter(published_date__gte=two_years_ago)
for book in books:
print(book)
timezone.now()— текущее время с учётом часового пояса проектаtimezone.timedelta(days=N)— временной интервал
Часть 2: Django REST Framework
7. Что такое DRF и зачем он нужен
Django REST Framework (DRF) — это мощный и гибкий набор инструментов для создания веб-API с использованием Django. DRF расширяет возможности Django и обеспечивает простоту разработки RESTful API.
Основные концепции
| Концепция | Описание |
|---|---|
| REST | Representational State Transfer — архитектурный стиль для веб-сервисов. Использует HTTP-методы (GET, POST, PUT, DELETE) |
| Ресурсы | Объекты или данные, к которым обращаются через URL |
| Статус-коды HTTP | 200 OK, 201 Created, 400 Bad Request, 404 Not Found и т.д. |
Архитектурные слои DRF
- Модели — определяют структуру данных (Django ORM)
- Сериализаторы — преобразуют сложные типы данных в JSON/XML и обратно
- Представления (Views) — обрабатывают HTTP-запросы и возвращают ответы
- URL-маршрутизация — направляет запросы к нужным представлениям
Почему стоит использовать DRF
- Простота разработки — декларативный подход, повторное использование кода через сериализаторы
- Мощные инструменты — встроенные механизмы аутентификации, авторизации, валидации
- Стандартизация — придерживается стандартов REST
- Расширяемость — модульная архитектура, легко настраивается
- Документация — автоматическая интерактивная документация API (Browsable API)
Где используется DRF
- API для мобильных приложений
- Веб-сервисы для интеграции с другими системами
- Микросервисная архитектура
8. Установка и подключение DRF
Шаг 1: Установка через pip
pip install djangorestframework
Шаг 2: Добавление в INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ... стандартные приложения Django ...
'rest_framework', # добавить DRF
'myapp', # ваше приложение
]
'rest_framework' в INSTALLED_APPS Django сможет находить шаблоны и статику DRF, включая Browsable API — интерактивный HTML-интерфейс для тестирования эндпоинтов прямо в браузере.
9. Сериализация в DRF
Сериализация — процесс преобразования сложных структур данных (объектов моделей, QuerySets) в простые форматы (JSON, XML), которые можно передать по сети.
Десериализация — обратный процесс: входящие данные (JSON) → объект модели.
Зачем нужны сериализаторы в DRF
- Преобразование данных: модель Django → JSON для клиента
- Валидация данных: проверка входящих данных перед сохранением
- Упрощение кода: готовые методы вместо ручной обработки
Преимущества сериализаторов
- Автоматизация: сериализаторы автоматизируют преобразование данных
- Единообразие: консистентное форматирование и проверка данных
- Удобство работы с API: стандартные механизмы для работы с данными
ModelSerializer (автоматически генерирует поля из модели), а также написание первого APIView/@api_view. Этот урок охватывает только введение в концепции DRF.