📖 Теория — Урок 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} — один словарь для всего QuerySet
  • annotate() → каждый объект получает дополнительный атрибут

Комбинирование 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.

Основные концепции

КонцепцияОписание
RESTRepresentational State Transfer — архитектурный стиль для веб-сервисов. Использует HTTP-методы (GET, POST, PUT, DELETE)
РесурсыОбъекты или данные, к которым обращаются через URL
Статус-коды HTTP200 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.