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

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

Возможно, вас заинтересует

Декоратор @property в моделях Django

В этой короткой заметке я покажу как использовать декоратор @property в моделях …