Как создавать пользовательские команды управления 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