💻 Примеры — Урок 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.