Как создавать пользовательские команды управления Django

В Django есть удобный инструмент командной строки manage.py, который помогаем выполнять различные действия например создание приложения Джанго, создание и выполнение миграций БД, запуск встроенного сервера и т.п. Приятной особенностью этого инструмента является то, что вы можете добавить в него свои собственные команды. Эти команды помогут взаимодействовать вам с вашим приложением из терминала или вы можете настроить выполнение этих команд по крону.
Ниже я покажу как добавить в manage.py собственные команды.
Давайте сначала посмотрим какие команды вы можете использовать, для этого в терминале выполните команду:
python manage.py help
Вы получите список команд разделенный на разделы. Чтобы получить помощь по конкретной команде, например runserver, выполните в терминале:
python manage.py help runserver
Создание собственных команд в manage.py
Команды создаются в ваших приложениях в подпапке /management/commands. Например я хочу добавить собственные команды в приложение блог, для этого я создаю management/commands/my_command.py
Имя файла это и есть имя команды, которое используется для ее вызова. Например чтобы вызвать команду из файла добавленного выше, в командной строке напишем:
python manage.py my_command
Основы. Базовый пример
Для демонстрации и тренировки создадим очень простую команду, которая возвращает нам текущее время.
Для этого создадим файлmanagement/commands/what_time_is_it.py:
from django.core.management.base import BaseCommand
from django.utils import timezone
class Command(BaseCommand):
help = 'Отображает текущее время'
def handle(self, *args, **kwargs):
time = timezone.now().strftime('%X')
self.stdout.write(f"Текущее время: {time}")
Весь файл состоит из одного класса Command, который наследуется от базового класса BaseCommand. В переменную help мы добавляем строку, которая выводится когда пользователь запрашивает справку с помощью manage.py help
Сам код команды определен внутри метода handle().
Теперь выполним нашу собственную команду добавленную нами в manage.py:
python manage.py what_time_is_it
И мы получим в выводе текущее время. Напомню, что имя команды совпадает с именем файла без окончания py.
А для чего вообще нужно так заморачиваться, почему бы не просто набросать нужные инструкции в пайтон скрипте и выполнить его? Используя такой подход с созданием собственных команд manage.py, мы можем импортировать модели Джанго и работать с базой данных с помощью ORM Django.
Обработка аргументов в командах
Django использует argparse, который является частью стандартной библиотеки Python. Для обработки аргументов в нашей пользовательской команде, мы должны определить метод с именем add_arguments.
Позиционные Аргументы
Создадим команду, которая создает случайно сгенерированных пльзователей. Эта команда будет принимать обязательный аргумент total, который определяет количество создаваемых пользователей.
Создадим файл management/commands/create_users.py:
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Создает случайных пользователей'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Указывает сколько пользователей необходимо создать')
def handle(self, *args, **kwargs):
total = kwargs['total']
for i in range(total):
User.objects.create_user(username=get_random_string(7), email='', password='1234567890')
Теперь давайте создадим 5 пользователей, выполним команду:
python manage.py create_users 5
Если сейчас проверить таблицу auth_user, мы увидим 5 новых пользователей со случайно сгенерированными username.
Во всех создаваемых нами командах, мы добавляем параметр help. Он содержит подсказку по команде, ее можно вызвать manage.py help имя команды, например:
python manage.py help create_users
Именованные аргументы
Именованные аргументы, в отличии от позиционных, могут передаваться в любом порядке. Для примера добавим к нашей команде создания случайных пользователей возможность добавление префикса к username. Префикс будем передавать именованным агруметом с именем prefix.
Вернемся к нашему файлу management/commands/create_users.py:
class Command(BaseCommand):
help = 'Создает случайных пользователей'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Указывает сколько пользователей необходимо создать')
# Опциональный аргумент
parser.add_argument('-P', '--prefix', type=str, help='Префикс имени пользователя', )
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
for i in range(total):
if prefix:
username = f'{prefix}_{get_random_string(7)}'
else:
username = get_random_string(7)
User.objects.create_user(username=username, email='', password='1234567890')
Создадим еще 5 пользователей в имени которых будет префикс custom_user
python manage.py create_users 5 --prefix custom_user
Так же эту команду можно записать так
python manage.py create_users 5 -P custom_user
Проверим базу и увидим 5 случайно сгенерированных пользователей без префикса (их мы получили раньше) в имени и 5 с префиксом
Флаги
Это необязательные аргументы команды, которые используются для обработки логических значений. Допустим, мы хотим добавить флаг --admin, чтобы указать нашей команде создать суперпользователя или создать обычного пользователя, если флаг отсутствует.
Продолжим дорабатывать файл management/commands/create_users.py:
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Создает случайных пользователей'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Указывает сколько пользователей необходимо создать')
# Опциональный аргумент
parser.add_argument('-P', '--prefix', type=str, help='Префикс имени пользователя', )
# Флаг
parser.add_argument('-A', '--admin', action='store_true', help='Дать пользователю права администратора')
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
admin = kwargs['admin']
for i in range(total):
if prefix:
username = f'{prefix}_{get_random_string(7)}'
else:
username = get_random_string(7)
if admin:
User.objects.create_superuser(username=username, email='', password='1234567890')
else:
User.objects.create_user(username=username, email='', password='1234567890')
Создадим 2 администраторов:
python manage.py create_users 2 --admin
Проверим базу и увидим что создалось 2 пользователя с правами суперпользователя.
Кстати, обратите внимания, что имена пользователей сгенерировались без префиска, поскольку в команде мы не указали опциональный аргумент --prefix
Произвольный список аргументов
Теперь создадим команду, которая бы удаляла пользователей по переданному списку их ID.
Создаем файл management/commands/delete_users.py:
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Удаляет пользователей'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='ID пользователя')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write(f'Пользователь {user.username} c ID {user_id} был удален!')
except User.DoesNotExist:
self.stdout.write(f'Пользователь с ID {user_id} не существует.')
Для начала попробуем удалить одного пользователя с id, например, 5
python manage.py delete_users 5
У нас в базе был такой пользователь, поэтому получаем ответ что пользователь удален:
Теперь попробуем опять удалить пользователя с ID 5 и пользователей которых мы создали ранее с правами администратора (ID 13 и 14).
Достаточно передать в команду delete_users наши id разделенные пробелами
python manage.py delete_users 5 13 14
Получим ответ о том что пользователя 5 уже не существует, а 13 и 14 успешно удалены:
Добавляем красоту
Вывод результатов выполнения команд можно стилизовать, чтобы выделить предупреждения или ошибки.
Добавим выделение в предыдущий пример с удалением пользователей:
class Command(BaseCommand):
help = 'Удаляет пользователей'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='ID пользователя')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write(self.style.SUCCESS(f'Пользователь {user.username} c ID {user_id} был удален!'))
except User.DoesNotExist:
self.stdout.write(self.style.WARNING(f'Пользователь с ID {user_id} не существует.'))
Выполним
python manage.py delete_users 6 7 13
Теперь вывод выглядит немного наглядней:
Подробнее о добавлении пользовательских командах и стилизации их вывода читайте в официальной документации.
Выполнение команд по крону
Если у вас есть задача, которая должна выполняться периодически, например, создание отчета каждый понедельник. Или, допустим, у вас есть веб-скраппер, который собирает данные с какого-то веб-сайта каждые 10 минут. Вы можете определить этот код как команду управления и просто добавить его в crontab вашего сервера следующим образом: Как то так:
0 4 * * * /home/mysite/venv/bin/python /home/mysite/mysite/manage.py my_custom_command