✅ Решения: самопроверка DRF-блока
⚡ Краткие ответы
- aggregate vs annotate: aggregate возвращает один словарь; annotate — добавляет поля каждому объекту QuerySet.
- validated_data: доступен только после успешного
is_valid(). - 201 Created — при успешном POST.
- ModelViewSet = APIView + все CRUD-методы автоматически.
- request.query_params.get('author', '') — получить GET-параметр.
Блок А: ORM — агрегация и аннотации
А-1. aggregate vs annotate
aggregate() вычисляет значение по всему QuerySet и возвращает словарь (одна строка результата).
annotate() добавляет вычисленное поле к каждому объекту QuerySet — результат остаётся QuerySet с дополнительным полем.
# aggregate — один словарь
stats = Book.objects.aggregate(total=Count('id'), avg=Avg('price'))
# {'total': 50, 'avg': 19.5}
# annotate — каждому объекту добавляется поле
books = Book.objects.values('author').annotate(cnt=Count('id'))
# [{'author': 'Orwell', 'cnt': 3}, ...]
А-2. Агрегация задач
from django.db.models import Count, Q
from django.utils import timezone
today = timezone.now().date()
stats = Task.objects.aggregate(
total=Count('id'),
done=Count('id', filter=Q(status='done')),
overdue=Count('id', filter=Q(deadline__lt=today) & ~Q(status='done')),
)
А-3. order_by и срезы
# 5 самых дорогих книг
top5 = Book.objects.order_by('-price')[:5]
# + вторичная сортировка по названию
top5 = Book.objects.order_by('-price', 'title')[:5]
А-4. Subquery и OuterRef
OuterRef('field') — ссылка на поле из внешнего запроса внутри подзапроса. Это как коррелированный подзапрос в SQL.
from django.db.models import OuterRef, Subquery, Min
subq = Book.objects.filter(
author=OuterRef('author')
).values('author').annotate(min_p=Min('price')).values('min_p')
books = Book.objects.annotate(min_author_price=Subquery(subq))
А-5. ExpressionWrapper
ExpressionWrapper нужен, когда Django ORM не может автоматически вывести тип результата выражения (например, при делении DecimalField на DecimalField).
from django.db.models import F, ExpressionWrapper, fields
books = Book.objects.annotate(
discount_pct=ExpressionWrapper(
(1 - F('discounted_price') / F('price')) * 100,
output_field=fields.FloatField()
)
)
Блок Б: Введение в DRF
Б-1. Зачем DRF
DRF добавляет поверх Django: автоматическую сериализацию/десериализацию, Browsable API, content negotiation, удобные классы views (APIView, Generic, ViewSet), аутентификацию, разрешения, throttling, версионирование.
Б-3. HTTP-статусы
| Ситуация | Статус |
|---|---|
| GET список | 200 OK |
| POST — создан объект | 201 Created |
| DELETE — удалён | 204 No Content |
| Ошибка валидации | 400 Bad Request |
| Не найден | 404 Not Found |
Блок В: Сериализаторы
В-2. is_valid и validated_data
is_valid() запускает валидацию: проверяет типы полей, обязательные поля, кастомные валидаторы. Только после успешной валидации данные попадают в validated_data. До вызова is_valid() этот атрибут не существует.
В-4. Кастомная валидация
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# Валидация отдельного поля
def validate_price(self, value):
if value < 0:
raise serializers.ValidationError("Цена не может быть отрицательной.")
return value
# Валидация нескольких полей
def validate(self, data):
discounted = data.get('discounted_price')
price = data.get('price')
if discounted and price and discounted >= price:
raise serializers.ValidationError(
"Скидочная цена должна быть меньше обычной."
)
return data
Блок Г: Представления
Г-3. ModelViewSet + Router
# views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# urls.py
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')
urlpatterns = router.urls
# Генерирует:
# GET /books/ — list
# POST /books/ — create
# GET /books/{pk}/ — retrieve
# PUT /books/{pk}/ — update
# PATCH /books/{pk}/ — partial_update
# DELETE /books/{pk}/ — destroy
Блок Д: query_params
Д-1. Фильтрация
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer
class BookFilterView(APIView):
def get(self, request):
qs = Book.objects.all()
author = request.query_params.get('author')
if author:
qs = qs.filter(author__icontains=author)
is_bestseller = request.query_params.get('is_bestseller')
if is_bestseller in ('true', '1'):
qs = qs.filter(is_bestseller=True)
serializer = BookSerializer(qs, many=True)
return Response(serializer.data)
Д-2. Пагинация
page = max(1, int(request.query_params.get('page', 1)))
# Ограничить page_size — защита от DoS
page_size = min(100, int(request.query_params.get('page_size', 10)))
start = (page - 1) * page_size
qs = qs[start:start + page_size]
Ограничение min(100, ...) защищает от запроса с ?page_size=100000.