💻 Примеры — Урок 36
⚡ Три примера урока
- Пример 1: модель Book с мягким удалением + SoftDeleteManager
- Пример 2: оптимизация ORM — select_related + prefetch_related
- Пример 3: создание Publisher + Book в транзакции с on_commit
Пример 1: Мягкое удаление модели Book
Полная реализация soft deletion: менеджер, модель, API-эндпоинты, тестирование через Postman.
managers.py
from django.db import models
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
models.py
from django.db import models
from .managers import SoftDeleteManager
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()
publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE,
null=True, blank=True)
created_at = models.DateTimeField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2,
null=True, blank=True)
discounted_price = models.DecimalField(max_digits=10, decimal_places=2,
null=True, blank=True)
is_bestseller = models.BooleanField(default=False)
genres = models.ManyToManyField('Genre', related_name='books')
is_banned = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False) # soft deletion
objects = SoftDeleteManager() # только не удалённые
all_objects = models.Manager() # все записи
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
def __str__(self):
return self.title
Миграция
python manage.py makemigrations
python manage.py migrate
Тестирование в Django shell
python manage.py shell
from books.models import Book
# Создаём книгу
book = Book.objects.get(pk=1)
# Мягкое удаление
book.delete()
# SQL: UPDATE books_book SET is_deleted=True WHERE id=1
# Физического DELETE нет!
# Теперь книга НЕ видна через стандартный менеджер
Book.objects.filter(pk=1).exists() # False
# Но данные в базе есть — через all_objects
Book.all_objects.filter(pk=1).exists() # True
Book.all_objects.get(pk=1).is_deleted # True
Пример 2: Оптимизация ленивой загрузки
Проблема: N+1 запросов
python manage.py shell
# Плохо: N+1 запросов
books = Book.objects.all() # 1 SELECT
for book in books:
print(book.publisher) # +1 SELECT для каждой книги
for genre in book.genres.all(): # ещё +N SELECT
print(genre)
Решение: select_related + prefetch_related
# Хорошо: 3 запроса вместо 1 + N + N*M
books = Book.objects.select_related('publisher').prefetch_related('genres').all()
# SQL 1: SELECT books JOIN publishers
# SQL 2: SELECT genres WHERE book_id IN (...)
for book in books:
print("Publisher:", book.publisher) # из кеша JOIN
for genre in book.genres.all(): # из кеша prefetch
print(" Genre:", genre)
views.py с оптимизацией
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
class BookListView(ListAPIView):
queryset = Book.objects.select_related('publisher').prefetch_related('genres')
serializer_class = BookSerializer
Пример 3: Создание Publisher + Book в транзакции
urls.py
from django.urls import path
from .views import create_book_with_publisher
urlpatterns = [
path('books/transactions/', create_book_with_publisher),
]
views.py — transaction.atomic + on_commit
from django.db import transaction
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Book, Publisher
from .serializers import BookSerializer
@api_view(['POST'])
def create_book_with_publisher(request):
def notify_success(book):
# В реальном проекте: отправка email, задача Celery и т.д.
print(f"\n{'—' * 25} Book '{book.title}' created! {'—' * 25}\n")
try:
with transaction.atomic():
publisher = Publisher.objects.create(
name=request.data.get('publisher_name', 'Default Publisher'),
established_date="2024-06-01"
)
book = Book.objects.create(
title=request.data.get('title', 'New Book'),
author=request.data.get('author', 'Unknown Author'),
published_date="2024-06-02",
publisher=publisher
)
# on_commit: выполнится только если транзакция завершилась успешно
transaction.on_commit(lambda: notify_success(book))
serializer = BookSerializer(book)
return Response(serializer.data, status=201)
except Exception as e:
# Транзакция уже откатилась — Publisher и Book не созданы
return Response({'error': str(e)}, status=400)
Тест через Postman
POST http://127.0.0.1:8000/books/transactions/
Content-Type: application/json
{
"title": "Django for Professionals",
"author": "William S. Vincent",
"publisher_name": "WelcomeToCode Press"
}
# Ответ 201:
{
"id": 42,
"title": "Django for Professionals",
"author": "William S. Vincent",
"published_date": "2024-06-02",
"publisher": 7,
"is_deleted": false
}
Пример с откатом — set_rollback
@api_view(['POST'])
def create_unique_publisher_book(request):
try:
with transaction.atomic():
publisher = Publisher.objects.create(name="New Publisher",
established_date="2024-06-01")
book = Book.objects.create(title="New Book", author="Author",
published_date="2024-06-02",
publisher=publisher)
# Откатить если Publisher с таким именем уже был
if Publisher.objects.filter(name="New Publisher").count() > 1:
transaction.set_rollback(True)
serializer = BookSerializer(book)
return Response(serializer.data)
except Exception as e:
return Response({'error': str(e)}, status=400)