Что это такое?
-Management Command — это обычный Python-скрипт, который «встраивается» в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через python manage.py ....
Зачем это нужно?
-Представьте ситуации:
--
-
- Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого). -
- Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта). -
- Нужно отправить e-mail рассылку пользователям. -
- Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов. -
Делать это через views.py (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом manage.py — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.
-Как это работает?
-Django использует систему «автообнаружения» (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, web):
-web/ -├── __init__.py -├── models.py -└── management/ <-- 1. Создаем папку management - ├── __init__.py - └── nginx/ - ├── commands/ <-- 2. Внутри неё папку commands - ├── __init__.py - └── my_cool_script.py <-- 3. Наш файл с командой-
Файл my_cool_script.py автоматически превратится в команду: python manage.py my_cool_script. Django будет искать его в папках management/commands/ внутри каждого установленного приложения. Это позволяет легко организовать код и держать все «команды обслуживания» в одном месте.
Для справки: Механизм Custom Management Commands появился еще в Django 0.96 (в глубокой древности) и с тех пор является стандартом де-факто для написания скриптов обслуживания.
-Анатомия команды
-Вот пример простейшей команды. Мы наследуемся от класса BaseCommand и переопределяем метод handle:
-# web/management/commands/hello.py -from django.core.management.base import BaseCommand +[0.1.6.post1] - 2026–03–19
+Изменено
+
-
+
- Новый алгоритм «висячей пунктуации» (HangingPunctuationProcessor). Добавлены компенсирующие пробелы для висячих символов, чтобы избежать наложения на соседние слова. Теперь «висячие символы» (кавычки, скобки и т. п.) оборачиваются в
<span>вместе с ближайшим словом и пробелом, что обеспечивает отсутствие визуальных смещений внутри строки. Режимboth(для одновременного вывешивания в обе стороны) отключен из-за потенциальных конфликтов компенсирующих пробелов и проблем с выравниванием при использовании CSStext-justify.
+
Исправлено
+-
+
- Автоматическая замена символов
x,X,х,Х, стоящих между числами, на знак умножения×, чтобы выражения вида100x100или100 х 100` корректно обрабатывались и выглядели типографски правильными (100×100или100 × 100).
+
Запуск:
--python manage.py hello Иван-
Увидим в консоли:
--Привет, Иван!-
Реальный пример: Массовая типографика через etpgrf
-В моём проекте, DQ – коллекция цитат: место для вдумчивого чтения, контент был частично обработан с помощью Типографа Муравьёа, частично вручную. И вот я установил и настроил etpgrf (тем более что Типограф Муравьёва «почил в бозе», да и до того не обновлялся с 2018 года). Все новые цитаты типографируются через etpgrf, но что делать со старыми? Их сотни, и открывать и «пересохранаять» каждую вручную — это адский труд. Вот тут на помощь и приходит Custom Management Command.
-Вот как я это реализовал. В web/management/commands/reprocess_typography.py:
-
-from django.core.management.base import BaseCommand
-from web.models import TbDictumAndQuotes
-# Импорты библиотеки etpgrf
-from etpgrf.typograph import Typographer
-from etpgrf.sanitizer import SanitizerProcessor
-# ... другие процессоры, если нужно ...
-
-class Command(BaseCommand):
- help = 'Переобрабатывает все цитаты'
-
- def add_arguments(self, parser):
- # Добавляем флаг "сухой прогон"
- parser.add_argument(
- '--dry-run',
- action='store_true',
- help='Запустить без сохранения изменений',
- )
- # Ограничение количества
- parser.add_argument('--limit', type=int, help='Сколько записей обработать')
- # Смещение (для пагинации)
- parser.add_argument('--offset', type=int, default=0, help='Сколько пропустить')
-
- def handle(self, *args, **options):
- # 1. Настраиваем типограф
- # ... инициализация процессоров ...
- settings = {
- # ...
- 'sanitizer': SanitizerProcessor(mode="html"), # Режим HTML-очистки
- 'hanging_punctuation': 'left', # Режим висячей пунктуации
- # ...
- }
- typographer = Typographer(**settings)
-
- # 2. Получаем записи из базы с учетом limit/offset
- qs = TbDictumAndQuotes.objects.all().order_by('id')
-
- start = options['offset']
- if options['limit']:
- qs = qs[start : start + options['limit']]
- else:
- qs = qs[start:]
-
- self.stdout.write(f"Найдено {qs.count()} цитат...")
-
- # 3. Бежим по циклу с прогресс-баром (tqdm если есть)
- try:
- from tqdm import tqdm
- iterator = tqdm(qs)
- except ImportError:
- iterator = qs
-
- for dq in iterator:
- try:
- # Обрабатываем текст
- new_html = typographer.process(dq.szContent)
-
- if options['dry_run']:
- self.stdout.write(f"[{dq.id}] Предпросмотр: {new_html[:50]}...")
- else:
- dq.szContentHTML = new_html
- dq.save(update_fields=['szContentHTML'])
-
- except Exception as e:
- self.stdout.write(self.style.ERROR(f"Ошибка id={dq.id}: {e}"))
-
-
- self.stdout.write(self.style.SUCCESS(f"\nГотово!"))
- Возможно вы заметили, что в моем проекте для контента есть два поля: szContent и szContentHTML. В первом хранится «сырой» текст, во втором — результат типографирования. В вашем проекте, скорее всего, только одно поле для контента, но по сути это ничего не меняет.
Еще интересные фишки, которые использовались при типографировании:
--
-
self.stdout.writeвместоprint– позволяет Django перехватывать вывод (например, для тестов) и корректно работать с кодировками.
- self.style.SUCCESSиself.style.ERROR– раскрашивает текст в консоли (зеленый/красный) и это очень удобно для визуального восприятия логов.
- - Аргумент --dry-run — позволяют безопасно тестировать скрипт на продакшене перед тем, как реально менять данные. -
- Аргументы --limit и --offset — позволяют обрабатывать базу «порциями», что полезно для больших объемов данных (можно запустить несколько процессов параллельно с разными offset). -
- Используется
update_fields— позволяет перезаписывать не всю модель целиком (что могло бы затереть изменения, сделанные кем-то другим в ту же секунду), а обновляем только конкретные поля.
-
Теперь, чтобы типографировать весь контент, нам достаточно одной строку в терминале, и Django сделает всю грязную работу за нас (не забудьте инициировать виртуальное окружение вашего проекта).
-Тестовый прогон (безопасно, ничего не сохраняет):
--python manage.py reprocess_typography --dry-run-
Боевой запуск, с изменениями данные в базе:
--python manage.py reprocess_typography-
Если у вас очень много записей, можно запускать по частям:
--python manage.py reprocess_typography --limit 1000 --offset 0 -python manage.py reprocess_typography --limit 1000 --offset 1000 -python manage.py reprocess_typography --limit 1000 --offset 2000+ ## [0.1.6] - 2024-03-19 +### Изменено +- Н +### +- diff --git a/etpgrf_site/typograph/templates/typograph/base.html b/etpgrf_site/typograph/templates/typograph/base.html index 7e61dea..ecc48f1 100644 --- a/etpgrf_site/typograph/templates/typograph/base.html +++ b/etpgrf_site/typograph/templates/typograph/base.html @@ -94,7 +94,7 @@