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

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

Создания файла с собственными командами для manage.py Django

Имя файла это и есть имя команды, которое используется для ее вызова. Например чтобы вызвать команду из файла добавленного выше, в командной строке напишем:

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 с префиксом

Созданные с помощью собственной команды manage.py пользователи

Флаги

Это необязательные аргументы команды, которые используются для обработки логических значений. Допустим, мы хотим добавить флаг —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

Использование флагов в собственных командах manage.py

Произвольный список аргументов

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

У нас в базе был такой пользователь, поэтому получаем ответ что пользователь удален:

Удаление пользователя с использованием собственной команды manage.py

Теперь попробуем опять удалить пользователя с ID 5 и пользователей которых мы создали ранее с правами администратора (ID 13 и 14).

Достаточно передать в команду delete_users наши id разделенные пробелами

python manage.py delete_users 5 13 14

Получим ответ о том что пользователя 5 уже не существует, а 13 и 14 успешно удалены:

Собственные команды manage.py с произвольным списком аргументов

Добавляем красоту

Вывод результатов выполнения команд можно стилизовать, чтобы выделить предупреждения или ошибки.

Добавим выделение в предыдущий пример с удалением пользователей:

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

Теперь вывод выглядит немного наглядней:

Кастомизация вывода команд manage.py

Подробнее о добавлении пользовательских командах и стилизации их вывода читайте в официальной документации.

Выполнение команд по крону

Если у вас есть задача, которая должна выполняться периодически, например, создание отчета каждый понедельник. Или, допустим, у вас есть веб-скраппер, который собирает данные с какого-то веб-сайта каждые 10 минут. Вы можете определить этот код как команду управления и просто добавить его в crontab вашего сервера следующим образом: Как то так:

0 4 * * * /home/mysite/venv/bin/python /home/mysite/mysite/manage.py my_custom_command

Хостинг для ваших проектов