💻 Примеры — Урок 26

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

⚡ Ключевые примеры

# aggregate() — одно значение
stats = Book.objects.aggregate(total=Count('id'), avg=Avg('price'))

# annotate() — поле на каждый объект
Book.objects.values('author').annotate(cnt=Count('id'))

# Subquery + OuterRef
sub = Book.objects.filter(author=OuterRef('author')).values('author').annotate(m=Min('price')).values('m')
Book.objects.annotate(min_p=Subquery(sub))

# DRF установка
# pip install djangorestframework
# INSTALLED_APPS: 'rest_framework'

Модель для примеров

Все примеры используют следующую модель Book из лекции:

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    def __str__(self): return self.name

class Publisher(models.Model):
    name = models.CharField(max_length=100)
    established_date = models.DateField()
    def __str__(self): return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    published_date = models.DateField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    discounted_price = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True
    )
    is_bestseller = models.BooleanField(default=False)
    publisher = models.ForeignKey(
        Publisher, on_delete=models.CASCADE, null=True, blank=True
    )
    created_at = models.DateTimeField(null=True, blank=True)

    def __str__(self): return self.title

Пример 1: aggregate() — базовые агрегации

from django.db.models import Avg, Count, Sum, Min, Max

# Общее количество книг и средняя цена
aggregates = Book.objects.aggregate(
    total_books=Count('id'),
    average_price=Avg('price')
)
print(f"Всего книг: {aggregates['total_books']}")
print(f"Средняя цена: {aggregates['average_price']}")

# Общая стоимость всех книг
total = Book.objects.aggregate(total_price=Sum('price'))
print(f"Общая стоимость: {total['total_price']}")

# Диапазон цен
price_range = Book.objects.aggregate(
    min_price=Min('price'),
    max_price=Max('price')
)
print(f"Самая дешёвая: {price_range['min_price']}")
print(f"Самая дорогая: {price_range['max_price']}")

Пример 2: annotate() — количество книг по авторам

from django.db.models import Count

# Группировка по полю author, аннотация количеством книг
author_stats = Book.objects.values('author').annotate(
    book_count=Count('id')
)
for entry in author_stats:
    print(f"Автор ID: {entry['author']}, Книг: {entry['book_count']}")

Пример 3: annotate() с ExtractYear

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')
).order_by('year')

for entry in books_per_year:
    print(f"Год: {entry['year']}, Книг: {entry['book_count']}")

# Средняя цена по кварталам
avg_per_quarter = Book.objects.annotate(
    quarter=ExtractQuarter('published_date')
).values('quarter').annotate(
    avg_price=Avg('price')
).order_by('quarter')

for entry in avg_per_quarter:
    print(f"Квартал: {entry['quarter']}, Средняя цена: {entry['avg_price']}")

Пример 4: order_by() — сортировка

from myapp.models import Book

# По одному полю
books = Book.objects.order_by('title')          # алфавит
books = Book.objects.order_by('-published_date') # новые сначала

# По нескольким полям
books = Book.objects.order_by('author__name', 'title')

# По полю связанной таблицы (только книги с издателем)
books = Book.objects.filter(
    publisher__isnull=False
).order_by('publisher__name')

for book in books:
    print(f"{book.title} — {book.publisher.name}")

Пример 5: Срезы QuerySet

# Первые 3 книги
books = Book.objects.all()[:3]

# Книги 4–6 (пропустить первые 3)
books = Book.objects.all()[3:6]

# 5 самых дешёвых книг
cheapest = Book.objects.order_by('price')[:5]
for book in cheapest:
    print(f"{book.title}: {book.price}")

# 5 самых новых
newest = Book.objects.order_by('-published_date')[:5]
for book in newest:
    print(f"{book.title}: {book.published_date}")

Пример 6: Subquery + OuterRef — аннотация минимальной ценой автора

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

# Подзапрос: минимальная цена книги того же автора
subquery = Book.objects.filter(
    author=OuterRef('author')   # ссылается на 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(f"{book.title} | Цена: {book.price} | Мин. у автора: {book.min_author_price}")

Пример 7: Subquery — минимальная цена по издательству

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

subquery = Book.objects.filter(
    publisher=OuterRef('publisher')
).values('publisher').annotate(
    min_price=Min('price')
).values('min_price')

books = Book.objects.annotate(
    min_publisher_price=Subquery(subquery)
)
for book in books:
    print(
        book.publisher.name if book.publisher else 'н/д',
        book.title,
        book.price,
        book.min_publisher_price,
        sep='  |  '
    )

Пример 8: Фильтрация книг ниже средней цены по издательству

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

subquery = Book.objects.filter(
    publisher=OuterRef('publisher')
).values('publisher').annotate(
    avg_price=Avg('price')
).values('avg_price')

books_below_avg = Book.objects.filter(
    price__lt=Subquery(subquery)
)
for book in books_below_avg:
    print(f"{book.title}: {book.price}")

Пример 9: Издательства с более чем одной книгой

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

subquery = Book.objects.filter(
    publisher=OuterRef('pk')   # OuterRef на первичный ключ Publisher
).values('publisher').annotate(
    book_count=Count('id')
).values('book_count')

publishers = Publisher.objects.annotate(
    book_count=Subquery(subquery)
).filter(book_count__gt=1)

for pub in publishers:
    print(f"{pub.name}: {pub.book_count} книг")

Пример 10: ExpressionWrapper — процент скидки

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

# Вычисляем процент скидки для каждой книги
books = Book.objects.filter(
    discounted_price__isnull=False  # только книги со скидкой
).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}% скидка")

Пример 11: Временные метки

from django.utils import timezone

# Книги за последние 2 года
two_years_ago = timezone.now() - timezone.timedelta(days=365 * 2)
recent_books = Book.objects.filter(published_date__gte=two_years_ago)

for book in recent_books:
    print(book.title, book.published_date)

Пример 12: Минимальный DRF-проект (TaskManager API)

# models.py
from django.db import models

class Task(models.Model):
    STATUS_CHOICES = [
        ('todo', 'To Do'),
        ('in_progress', 'In Progress'),
        ('done', 'Done'),
    ]
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='todo')
    deadline = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self): return self.title
# serializers.py
from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'description', 'status', 'deadline', 'created_at']
        read_only_fields = ['id', 'created_at']
# 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(['GET'])
def task_detail(request, pk):
    """Получение задачи по ID."""
    try:
        task = Task.objects.get(pk=pk)
    except Task.DoesNotExist:
        return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND)
    serializer = TaskSerializer(task)
    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)
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('tasks/', views.task_list, name='task-list'),
    path('tasks/create/', views.task_create, name='task-create'),
    path('tasks/<int:pk>/', views.task_detail, name='task-detail'),
]
Этот пример — минимальная база DRF-проекта. В следующих уроках он будет расширен через APIView, ModelViewSet и Router.