165 lines
7.3 KiB
Python
165 lines
7.3 KiB
Python
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} генераций в базе данных!")
|
||
)
|