tmp: Массовая типографика контента в Django: пользовательские команды управления (вёрстка в песочнице fin).
This commit is contained in:
@@ -33,35 +33,31 @@
|
|||||||
<div class="post-content mt-4">
|
<div class="post-content mt-4">
|
||||||
|
|
||||||
<h2>Что это такое?</h2>
|
<h2>Что это такое?</h2>
|
||||||
|
<p><strong>Management Command</strong> — это обычный Python-скрипт, который «встраивается» в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через <code>python manage.py ...</code>.</p>
|
||||||
<p><strong>Management Command</strong> — это обычный Python-скрипт, который "встраивается" в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через <code>python manage.py ...</code>.</p>
|
|
||||||
|
|
||||||
<h2>Зачем это нужно?</h2>
|
<h2>Зачем это нужно?</h2>
|
||||||
<p>Представьте ситуации:</p>
|
<p>Представьте ситуации:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого).</li>
|
<li>Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого).</li>
|
||||||
<li>Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта).</li>
|
<li>Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта).</li>
|
||||||
<li>Нужно отправить e-mail рассылку пользователям.</li>
|
<li>Нужно отправить e-mail рассылку пользо­вателям.</li>
|
||||||
<li>Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов.</li>
|
<li>Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Делать это через <tt>views.py</tt> (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом <tt>manage.py</tt> — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.</p>
|
<p>Делать это через <tt>views.py</tt> (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом <tt>manage.py</tt> — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.</p>
|
||||||
|
<h2>Как это работает?</h2>
|
||||||
<h2>Как это работает?</h2>
|
<p>Django использует систему «автообна­ружения» (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, <tt>web</tt>):</p>
|
||||||
<p>Django использует систему "автообнаружения" (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, <tt>web</tt>):</p>
|
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">web/
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">web/
|
||||||
├── __init__.py
|
├── __init__.py
|
||||||
├── models.py
|
├── models.py
|
||||||
└── management/ 🠀 1. Создаем папку management
|
└── management/ <-- 1. Создаем папку management
|
||||||
├── __init__.py
|
├── __init__.py
|
||||||
└── nginx/
|
└── nginx/
|
||||||
├── commands/ 🠀 2. Внутри неё папку commands
|
├── commands/ <-- 2. Внутри неё папку commands
|
||||||
├── __init__.py
|
├── __init__.py
|
||||||
└── my_cool_script.py 🠀 3. Наш файл с командой</pre>
|
└── my_cool_script.py <-- 3. Наш файл с командой</pre>
|
||||||
<p>Файл <tt>my_cool_script.py</tt> автоматически превратится в команду: <code>python manage.py my_cool_script</code>. Django будет искать его в папках <tt>management/commands/</tt> внутри каждого установленного приложения. Это позволяет легко организовать код и держать все "команды обслуживания" в одном месте.</p>
|
<p>Файл <tt>my_cool_script.py</tt> автома­тически превратится в команду: <code>python manage.py my_cool_script</code>. Django будет искать его в папках <tt>management/commands/</tt> внутри каждого устано­вленного приложения. Это позволяет легко организовать код и держать все «команды обслуживания» в одном месте.</p>
|
||||||
<p><strong>Для справки:</strong> Механизм Custom Management Commands появился еще в Django 0.96 (в глубокой древности) и с тех пор является стандартом де-факто для написания скриптов обслуживания.</p>
|
<p><strong>Для справки:</strong> Механизм Custom Management Commands появился еще в Django 0.96 (в глубокой древности) и с тех пор является стандартом де-факто для написания скриптов обслуживания.</p>
|
||||||
|
|
||||||
<h2>Анатомия команды</h2>
|
<h2>Анатомия команды</h2>
|
||||||
<p>Вот пример простейшей команды. Мы наследуемся от класса <code>BaseCommand</code> и переопределяем метод <code>handle</code>:</p>
|
<p>Вот пример простейшей команды. Мы наследуемся от класса <code>BaseCommand</code> и переопре­деляем метод <code>handle</code>:</p>
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
# web/management/commands/hello.py
|
# web/management/commands/hello.py
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
@@ -77,20 +73,16 @@ class Command(BaseCommand):
|
|||||||
# Главная логика
|
# Главная логика
|
||||||
name = options['name']
|
name = options['name']
|
||||||
self.stdout.write(f"Привет, {name}!")</pre>
|
self.stdout.write(f"Привет, {name}!")</pre>
|
||||||
|
<p>Запуск:</p>
|
||||||
Запуск:
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
```bash
|
python manage.py hello Иван</pre>
|
||||||
python manage.py hello Иван
|
<p>Увидим в консоли:</p>
|
||||||
# Вывод: Привет, Иван!
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
```
|
Привет, Иван!</pre>
|
||||||
|
<h2>Реальный пример: Массовая типографика через etpgrf</h2>
|
||||||
## Реальный пример: Массовая типографика
|
<p>В моём проекте, <a href="https://dq.cube2.ru/" target="_blank">DQ – коллекция цитат. Место для вдумчивого чтения</a>, контент был частично обработан с помощью Типографа Муравьёа, частично вручную. И вот я установил и настроил etpgrf (тем более что Типограф Муравьева «почил в бозе», да и до того не обновлялся с 2018 года). Все новые цитаты типогра­фируются через etpgrf, но что делать со старыми? Их сотни, и открывать и «пересо­хранаять» каждую вручную — это адский труд. Вот тут на помощь и приходит Custom Management Command.</p>
|
||||||
|
<p>Вот как я это реализовал: в <tt>web/management/commands/reprocess_typography.py</tt>:</p>
|
||||||
В нашем проекте возникла задача: у нас есть тысячи цитат, сохраненных со старой разметкой. Мы хотим "прогнать" их все через новый типограф `etpgrf` с новыми настройками (висячая пунктуация слева).
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
|
||||||
Вот как мы это реализовали в `reprocess_typography.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from web.models import TbDictumAndQuotes
|
from web.models import TbDictumAndQuotes
|
||||||
# Импорты библиотеки etpgrf
|
# Импорты библиотеки etpgrf
|
||||||
@@ -157,27 +149,29 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.ERROR(f"Ошибка id={dq.id}: {e}"))
|
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Готово!"))</pre>
|
||||||
```
|
<p>Возможно вы заметили, что в моем проекте для контента есть два поля: <code>szContent</code> и <code>szContentHTML</code>. В первом хранится «сырой» текст, во втором — результат типогра­фирования. В вашем проекте, скорее всего только одно поле для контента, но по сути это ничего не меняет.</p>
|
||||||
|
<p>Еще интересные фишки, которые исполь­зовались при типогра­фировании:</p>
|
||||||
|
<ol>
|
||||||
|
<li><code>self.stdout.write</code> вместо <code>print</code> – позволяет Django перехва­тывать вывод (например, для тестов) и корректно работать с кодировками.</li>
|
||||||
|
<li><code>self.style.SUCCESS</code> и <code>self.style.ERROR</code> – раскрашивает текст в консоли (зеленый/красный) и это очень удобно для визуального восприятия логов.</li>
|
||||||
|
<li>Аргумент <tt>–dry-run</tt> — позволяют безопасно тестировать скрипт на продакшене перед тем, как реально менять данные.</li>
|
||||||
|
<li>Аргументы <tt>–limit</tt> и <tt>–offset</tt> — позволяют обрабатывать базу «порциями», что полезно для больших объемов данных (можно запустить несколько процессов параллельно с разными offset).</li>
|
||||||
|
<li>Используется <code>update_fields</code> – позволяет переза­писывать не всю модель целиком (что могло бы затереть изменения, сделанные кем-то другим в ту же секунду), а обновляем только конкретные поля.</li>
|
||||||
|
</ol>
|
||||||
|
<p>Теперь, чтобы типогра­фировать весь контент, нам достаточно одной строку в терминале, и Django сделает всю грязную работу за нас (не забудьте инициировать виртуальное окружение вашего проекта).</p>
|
||||||
|
<p>Тестовый прогон (безопасно, ничего не сохраняет):</p>
|
||||||
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
python manage.py reprocess_typography --dry-run</pre>
|
||||||
|
<p>Боевой запуск, с изменениями данные в базе:</p>
|
||||||
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
python manage.py reprocess_typography</pre>
|
||||||
|
<p>Если у вас очень много записей, можно запускать по частям:</p>
|
||||||
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
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</pre>
|
||||||
|
|
||||||
### Фишки, которые мы использовали:
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user