🔖 Справочник — Урок 26

← К оглавлению урока

⚡ Быстрый справочник

# Агрегация всего QuerySet
Model.objects.aggregate(alias=Func('field'))

# Аннотация каждого объекта
Model.objects.values('group').annotate(alias=Func('field'))

# Сортировка / ограничение
Model.objects.order_by('-field')[:10]

# Подзапрос
subq = Model.objects.filter(fk=OuterRef('fk')).values('fk').annotate(v=Min('price')).values('v')
Model.objects.annotate(min_v=Subquery(subq))

# DRF: установка
pip install djangorestframework
# settings.py -> INSTALLED_APPS: 'rest_framework'

Агрегационные функции

from django.db.models import Count, Sum, Avg, Min, Max
ФункцияСинтаксисНазначение
CountCount('id') / Count('id', distinct=True)Количество записей
SumSum('price')Сумма значений поля
AvgAvg('price')Среднее значение
MinMin('price')Минимальное значение
MaxMax('price')Максимальное значение

Метод aggregate()

# Возвращает словарь
result = Model.objects.aggregate(
    alias1=Func1('field1'),
    alias2=Func2('field2'),
)
# result = {'alias1': значение, 'alias2': значение}

# Пример
stats = Book.objects.aggregate(
    total=Count('id'),
    avg_price=Avg('price'),
    min_price=Min('price'),
    max_price=Max('price'),
    total_revenue=Sum('price'),
)

Метод annotate()

# Добавляет вычисленное поле к каждому объекту QuerySet
# Обычно используется с values() для группировки

# Количество книг по авторам
Book.objects.values('author').annotate(book_count=Count('id'))

# Аннотация без группировки (к каждому объекту)
Book.objects.annotate(discounted=F('price') * 0.9)

# Комбинация: год публикации + количество
from django.db.models.functions import ExtractYear
Book.objects.annotate(
    year=ExtractYear('published_date')
).values('year').annotate(count=Count('id'))

Метод order_by()

# ASC (по возрастанию)
Model.objects.order_by('field')

# DESC (по убыванию) — минус перед именем поля
Model.objects.order_by('-field')

# По нескольким полям
Model.objects.order_by('field1', '-field2')

# По полю связанной модели (двойное подчёркивание)
Book.objects.order_by('publisher__name', 'title')

# Сортировка по аннотированному полю
Book.objects.annotate(cnt=Count('id')).order_by('-cnt')

Срезы QuerySet

# QuerySet[start:stop]  →  SQL: LIMIT (stop-start) OFFSET start

QuerySet[:5]     # первые 5    → LIMIT 5
QuerySet[5:10]   # с 6 по 10  → LIMIT 5 OFFSET 5
QuerySet[0:1]    # только 1   → LIMIT 1 (аналог first())

# Комбинация с order_by
Book.objects.order_by('price')[:10]           # 10 самых дешёвых
Book.objects.order_by('-published_date')[:5]  # 5 самых новых

Подзапросы: Subquery и OuterRef

from django.db.models import OuterRef, Subquery, Min, Avg, Count

# Шаблон коррелированного подзапроса:
inner_qs = InnerModel.objects.filter(
    fk_field=OuterRef('fk_field')   # ссылка на поле внешнего запроса
).values('fk_field').annotate(
    computed=AggrFunc('field')
).values('computed')               # возвращает одно значение

outer_qs = OuterModel.objects.annotate(
    alias=Subquery(inner_qs)
)

# Фильтрация через Subquery
OuterModel.objects.filter(field__lt=Subquery(inner_qs))

ExpressionWrapper

from django.db.models import F, ExpressionWrapper, fields

# Синтаксис
ExpressionWrapper(
    expression,                     # арифметическое выражение
    output_field=fields.ТипПоля()  # обязательно!
)

# Доступные типы output_field
fields.FloatField()
fields.DecimalField(max_digits=10, decimal_places=2)
fields.IntegerField()
fields.DurationField()

# Пример
Book.objects.annotate(
    discount_pct=ExpressionWrapper(
        (1 - F('discounted_price') / F('price')) * 100,
        output_field=fields.FloatField()
    )
)

Временные метки

from django.utils import timezone

# Текущее время (с учётом часового пояса)
now = timezone.now()

# Временной интервал
delta = timezone.timedelta(days=30)
delta = timezone.timedelta(weeks=2)
delta = timezone.timedelta(hours=24)

# Фильтрация по относительным датам
recent = Model.objects.filter(
    created_at__gte=timezone.now() - timezone.timedelta(days=7)
)

DRF: установка и подключение

# 1. Установка
pip install djangorestframework

# Фиксация в requirements.txt
pip freeze > requirements.txt

# 2. Добавление в INSTALLED_APPS (settings.py)
INSTALLED_APPS = [
    ...
    'rest_framework',
]

# 3. Опциональные настройки (settings.py)
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
    ],
}

DRF: минимальный сериализатор

# serializers.py
from rest_framework import serializers
from .models import Task

# ModelSerializer — автоматически создаёт поля из модели
class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'description', 'status', 'deadline']
        # или fields = '__all__'  для всех полей

# Использование в shell
from myapp.serializers import TaskSerializer
from myapp.models import Task

task = Task.objects.first()
serializer = TaskSerializer(task)
print(serializer.data)   # OrderedDict с JSON-совместимыми данными

DRF: минимальный @api_view

# views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Task
from .serializers import TaskSerializer

@api_view(['GET'])
def task_list(request):
    tasks = Task.objects.all()
    serializer = TaskSerializer(tasks, many=True)
    return Response(serializer.data)

@api_view(['POST'])
def task_create(request):
    serializer = TaskSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)