-

Что это такое?

-

Management Command — это обычный Python-скрипт, который «встраивается» в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через python manage.py ....

-

Зачем это нужно?

-

Представьте ситуации:

- -

Делать это через 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

+
Изменено
+ +

Исправлено

+ -class Command(BaseCommand): - help = 'Выводит приветствие' # Описание для --help - - def add_arguments(self, parser): - # Можно добавлять аргументы, как в argparse - parser.add_argument('name', type=str, help='Имя пользователя') - - def handle(self, *args, **options): - # Главная логика - name = options['name'] - self.stdout.write(f"Привет, {name}!")
-

Запуск:

-
-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. В первом хранится «сырой» текст, во втором — результат типогра­фирования. В вашем проекте, скорее всего, только одно поле для контента, но по сути это ничего не меняет.

-

Еще интересные фишки, которые исполь­зовались при типогра­фировании:

-
    -
  1. self.stdout.write вместо print – позволяет Django перехва­тывать вывод (например, для тестов) и корректно работать с кодировками.
  2. -
  3. self.style.SUCCESS и self.style.ERROR – раскрашивает текст в консоли (зеленый/красный) и это очень удобно для визуального восприятия логов.
  4. -
  5. Аргумент --dry-run — позволяют безопасно тестировать скрипт на продакшене перед тем, как реально менять данные.
  6. -
  7. Аргументы --limit и --offset — позволяют обрабатывать базу «порциями», что полезно для больших объемов данных (можно запустить несколько процессов параллельно с разными offset).
  8. -
  9. Используется update_fields — позволяет переза­писывать не всю модель целиком (что могло бы затереть изменения, сделанные кем-то другим в ту же секунду), а обновляем только конкретные поля.
  10. -
-

Теперь, чтобы типогра­фировать весь контент, нам достаточно одной строку в терминале, и 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 @@
© Sergei Erjemin, 2025–{% now 'Y' %}. - v0.1.6.post1 / v0.2.7 + v0.1.6.post1 / v0.2.8 {# Сводная статистика (HTMX) #} ... diff --git a/pyproject.toml b/pyproject.toml index 6182173..cc80665 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "2026-etpgrf-site" -version = "0.2.7" +version = "0.2.8" description = "" authors = ["erjemin "] readme = "README.md"