Что это такое?
- -Management Command — это обычный Python-скрипт, который "встраивается" в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через python manage.py ....
Management Command — это обычный Python-скрипт, который «встраивается» в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через python manage.py ....
Зачем это нужно?
Представьте ситуации:
-
-
- Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого). -
- Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта). -
- Нужно отправить e-mail рассылку пользователям. -
- Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов. +
- Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого). +
- Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта). +
- Нужно отправить e-mail рассылку пользователям. +
- Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов.
Делать это через views.py (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом manage.py — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.
- -Как это работает?
-Django использует систему "автообнаружения" (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, web):
+Делать это через views.py (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом manage.py — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.
+Как это работает?
+Django использует систему «автообнаружения» (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, web):
web/
├── __init__.py
├── models.py
-└── management/ 🠀 1. Создаем папку management
+└── management/ <-- 1. Создаем папку management
├── __init__.py
└── nginx/
- ├── commands/ 🠀 2. Внутри неё папку commands
+ ├── 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 (в глубокой древности) и с тех пор является стандартом де-факто для написания скриптов обслуживания.
- + └── 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:
Вот пример простейшей команды. Мы наследуемся от класса BaseCommand и переопределяем метод handle:
# web/management/commands/hello.py
from django.core.management.base import BaseCommand
@@ -77,20 +73,16 @@ class Command(BaseCommand):
# Главная логика
name = options['name']
self.stdout.write(f"Привет, {name}!")
-
-Запуск:
-```bash
-python manage.py hello Иван
-# Вывод: Привет, Иван!
-```
-
-## Реальный пример: Массовая типографика
-
-В нашем проекте возникла задача: у нас есть тысячи цитат, сохраненных со старой разметкой. Мы хотим "прогнать" их все через новый типограф `etpgrf` с новыми настройками (висячая пунктуация слева).
-
-Вот как мы это реализовали в `reprocess_typography.py`:
-
-```python
+ Запуск:
++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
@@ -157,27 +149,29 @@ class Command(BaseCommand):
self.stdout.write(self.style.ERROR(f"Ошибка id={dq.id}: {e}"))
- self.stdout.write(self.style.SUCCESS(f"\nГотово!"))
-```
+ 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-### Фишки, которые мы использовали: - -1. **`self.stdout.write` вместо `print`**: Это важно. Это позволяет Django перехватывать вывод (например, для тестов) и корректно работать с кодировками. -2. **`self.style.SUCCESS / ERROR`**: Раскрашивает текст в консоли (зеленый/красный). Очень удобно для визуального восприятия логов. -3. **Аргументы (`--dry-run`)**: Позволяют безопасно тестировать скрипт на продакшене перед тем, как реально менять данные. -4. **`update_fields`**: При сохранении мы не перезаписываем всю модель целиком (что могло бы затереть изменения, сделанные кем-то другим в ту же секунду), а обновляем только конкретные колонки. - -Теперь, чтобы "починить" всю базу, нам достаточно набрать одну строку в терминале, и Django сделает всю грязную работу за нас. - -## Запуск - -```bash -# Тестовый прогон (безопасно, ничего не сохраняет) -python manage.py reprocess_typography --dry-run - -# Боевой запуск (изменяет данные в базе!) -python manage.py reprocess_typography -```