add: management command to rename item titles to English with options for preview, batch updates, and custom seed configurations
HYPN0 Build and Push Docker Image / build-and-push (push) Successful in 37s

This commit is contained in:
2026-09-27 19:29:16 +03:00
parent 1c846f7912
commit e7f348d95e
@@ -0,0 +1,164 @@
import re
from typing import Optional
from django.core.management.base import BaseCommand
from django.db import transaction
from hypn0_site.models import TbHypn0Item
from hypn0_site.services.naming import generate_hypno_title
class Command(BaseCommand):
"""
Массовое переименование названий генераций (TbHypn0Item.s_title) на английский язык.
АРХИТЕКТУРНОЕ НАЗНАЧЕНИЕ:
=========================
1. Локализация каталога и прод-базы:
Переводит существующие русскоязычные названия картин в базе данных
на сочные англоязычные гипнотические тайтлы в стиле Гипножабы.
2. Детерминированность vs Случайность:
По умолчанию использует детерминированный сид на базе ID генерации (item.id + seed_offset),
что обеспечивает стабильность результатов при повторных запусках (идемпотентность).
Флаг --random позволяет генерировать полностью случайные тайтлы.
3. Безопасность и оптимизация:
- Пакетное обновление через bulk_update порциями (--batch-size).
- Режим предварительного просмотра (--dry-run).
- Фильтрация по умолчанию только кириллических / пустых названий (или всех с флагом --all).
"""
help = "Массовое переименование названий генераций на английский язык"
def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Тестовый прогон: показать изменения без сохранения в базу данных",
)
parser.add_argument(
"--all",
action="store_true",
help="Переименовать абсолютно все генерации (по умолчанию только с кириллицей или пустые)",
)
parser.add_argument(
"--only-cyrillic",
action="store_true",
help="Переименовывать только записи с кириллическими символами в названии (включено по умолчанию)",
)
parser.add_argument(
"--batch-size",
type=int,
default=500,
help="Размер пакета для bulk_update (по умолчанию: 500)",
)
parser.add_argument(
"--limit",
type=int,
default=None,
help="Ограничить количество обрабатываемых записей",
)
parser.add_argument(
"--seed-offset",
type=int,
default=0,
help="Смещение для детерминированного генератора сидов (по умолчанию: 0)",
)
parser.add_argument(
"--random",
action="store_true",
help="Использовать недетерминированные случайные сиды вместо ID записи",
)
def handle(self, *args, **options):
dry_run: bool = options["dry_run"]
rename_all: bool = options["all"]
batch_size: int = options["batch_size"]
limit: Optional[int] = options["limit"]
seed_offset: int = options["seed_offset"]
use_random: bool = options["random"]
self.stdout.write(
self.style.NOTICE(
f"Старт процесса переименования генераций "
f"(dry_run={dry_run}, rename_all={rename_all}, batch_size={batch_size}, random={use_random})..."
)
)
cyrillic_pattern = re.compile(r"[а-яА-ЯёЁ]")
qs = TbHypn0Item.objects.all().only("id", "s_title").order_by("id")
total_in_db = qs.count()
if total_in_db == 0:
self.stdout.write(self.style.WARNING("В базе данных нет генераций для обработки."))
return
to_update = []
renamed_count = 0
preview_samples = []
for item in qs.iterator(chunk_size=batch_size):
old_title = item.s_title or ""
# Проверяем критерий необходимости переименования
needs_rename = False
if rename_all:
needs_rename = True
else:
# По умолчанию: кириллица или пустой заголовок
if not old_title.strip() or cyrillic_pattern.search(old_title):
needs_rename = True
if not needs_rename:
continue
# Генерация нового англоязычного тайтла
seed = None if use_random else (item.id + seed_offset)
new_title = generate_hypno_title(seed=seed)
if len(preview_samples) < 15:
preview_samples.append((item.id, old_title, new_title))
item.s_title = new_title
to_update.append(item)
renamed_count += 1
if limit is not None and renamed_count >= limit:
break
# Пакетное сохранение порциями
if not dry_run and len(to_update) >= batch_size:
with transaction.atomic():
TbHypn0Item.objects.bulk_update(to_update, fields=["s_title"])
to_update.clear()
# Сохранение остатка
if not dry_run and to_update:
with transaction.atomic():
TbHypn0Item.objects.bulk_update(to_update, fields=["s_title"])
to_update.clear()
# Вывод примеров переименования
if preview_samples:
self.stdout.write("\nПримеры переименований (первые до 15 записей):")
self.stdout.write("-" * 70)
for item_id, old_t, new_t in preview_samples:
self.stdout.write(f" [ID {item_id:5d}] «{old_t}» -> «{new_t}»")
self.stdout.write("-" * 70)
self.stdout.write(f"\nВсего записей в базе данных: {total_in_db}")
self.stdout.write(f"Отобрано для переименования: {renamed_count}")
if dry_run:
self.stdout.write(
self.style.WARNING(
f"\n[DRY RUN] Проверено {renamed_count} записей. Изменения НЕ сохранены в БД. "
"Запустите без флага --dry-run для применения."
)
)
else:
self.stdout.write(
self.style.SUCCESS(f"\nУспешно переименовано {renamed_count} генераций в базе данных!")
)