From 3357f01c40d468e558156ab2a59f5e6427ceed50 Mon Sep 17 00:00:00 2001 From: erjemin Date: Thu, 19 Feb 2026 02:01:14 +0300 Subject: [PATCH] =?UTF-8?q?add:=20Management=20Command=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BC=D0=B0=D1=81=D1=81=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D1=82=D0=B8=D0=BF=D0=BE=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B5=D0=B9.=20=D0=98=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20`python=20dicquo/manage.py=20r?= =?UTF-8?q?eprocess=5Ftypography`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dicquo/web/management/__init__.py | 0 dicquo/web/management/commands/__init__.py | 0 .../commands/reprocess_typography.py | 105 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 dicquo/web/management/__init__.py create mode 100644 dicquo/web/management/commands/__init__.py create mode 100644 dicquo/web/management/commands/reprocess_typography.py diff --git a/dicquo/web/management/__init__.py b/dicquo/web/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dicquo/web/management/commands/__init__.py b/dicquo/web/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dicquo/web/management/commands/reprocess_typography.py b/dicquo/web/management/commands/reprocess_typography.py new file mode 100644 index 0000000..c5a4fe9 --- /dev/null +++ b/dicquo/web/management/commands/reprocess_typography.py @@ -0,0 +1,105 @@ +from django.core.management.base import BaseCommand +from web.models import TbDictumAndQuotes +try: + from etpgrf.typograph import Typographer + from etpgrf.layout import LayoutProcessor + from etpgrf.hyphenation import Hyphenator +except ImportError: + print("Ошибка: библиотека etpgrf не найдена. Пожалуйста, установите её через 'poetry add etpgrf'") + Typographer = None + +class Command(BaseCommand): + help = 'Переобрабатывает все цитаты через etpgrf с "санитайзером" и "висячей пунктуацией: слева"' + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Запустить без сохранения изменений в БД', + ) + parser.add_argument( + '--limit', + type=int, + help='Ограничить количество обрабатываемых записей', + ) + + def handle(self, *args, **options): + if not Typographer: + self.stdout.write(self.style.ERROR('Библиотека Etpgrf отсутствует.')) + return + + # Настройки типографа (как просил пользователь) + # 1. Layout + layout = LayoutProcessor( + langs=['ru'], + process_initials_and_acronyms=True, + process_units=True + ) + + # 2. Hyphenation + hyphenation = Hyphenator( + langs=['ru'], + max_unhyphenated_len=12 + ) + + settings = { + 'langs': ['ru'], + 'process_html': True, # Обрабатываем как HTML (чтобы не ломать структуру, если она есть) + 'quotes': True, + 'layout': layout, + 'unbreakables': True, + 'hyphenation': hyphenation, + 'symbols': True, + 'hanging_punctuation': 'left', # ВАЖНО: Слева + 'mode': 'mixed', + 'sanitizer': 'etp', # ВАЖНО: Санитайзинг включен (очистит старую разметку) + } + + self.stdout.write(f"Настройка Типографа с параметрами: {settings}") + typographer = Typographer(**settings) + + qs = TbDictumAndQuotes.objects.all() + if options['limit']: + qs = qs[:options['limit']] + + count = qs.count() + self.stdout.write(f"Найдено {count} цитат для обработки...") + + processed_count = 0 + + for dq in qs: + try: + # Берем исходный текст. + # Если в szContent уже лежит старый HTML (Муравьев), санитайзер 'etp' его вычистит. + source_text = dq.szContent + + if not source_text: + continue + + new_html = typographer.process(source_text) + + # Обрабатываем intro если есть + new_intro_html = "" + if dq.szIntro: + new_intro_html = typographer.process(dq.szIntro) + + if options['dry_run']: + self.stdout.write(f"[{dq.id}] Будет обновлено. Предпросмотр: {new_html[:50]}...") + else: + dq.szContentHTML = new_html + if new_intro_html: + dq.szIntroHTML = new_intro_html + + # Сохраняем в обход метода save(), чтобы не триггерить ничего лишнего, + # или вызываем save(), если там теперь пусто (в нашей новой моделе save пустой). + # Используем update_fields для скорости. + dq.save(update_fields=['szContentHTML', 'szIntroHTML']) + + processed_count += 1 + if processed_count % 10 == 0: + self.stdout.write(f"Обработано {processed_count}/{count}...", ending='\r') + + except Exception as e: + self.stdout.write(self.style.ERROR(f"Ошибка обработки id={dq.id}: {e}")) + + self.stdout.write(self.style.SUCCESS(f"\nГотово! Обработано {processed_count} цитат."))