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

Django распространяется с различными утилитами (командами), выполняемыми в командной строке, которые вызываются с помощью скрипта django-admin.py или manage.py (Custom Django Management Commands). Хорошая вещь в том, что вы также можете добавить свои собственные команды. Они могут хорошо помочь, когда необходимо взаимодействовать с приложением из командной строки терминала, а также их можно использовать в cron — утилите, использующейся для периодического выполнения заданий в определённое время.

Вступление

Перед тем, как начнем писать свои команды, ознакомимся с интерфейсом команднйо строки Django. Как минимум, при начале разработки в Django, вы должны были ознакомиться с такими командами, как startproject, runserver и collectstatic. Полный перечень встроенных команд можно узнать из справки:

python manage.py help

Результат будет примерно таким:

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
    changepassword
    createsuperuser

[contenttypes]
    remove_stale_contenttypes

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver

[sessions]
    clearsessions

[staticfiles]
    collectstatic
    findstatic
    runserver

Свои команды создаются в приложениях: создается каталог management, с вложенным каталогом commands. Пример:

mysite/                                   <-- каталог проекта
 |-- core/                                <-- каталог приложения
 |    |-- management/
 |    |    +-- commands/
 |    |         +-- my_custom_command.py  <-- модуль с кодом команды
 |    |-- migrations/
 |    |    +-- __init__.py
 |    |-- __init__.py
 |    |-- admin.py
 |    |-- apps.py
 |    |-- models.py
 |    |-- tests.py
 |    +-- views.py
 |-- mysite/
 |    |-- __init__.py
 |    |-- settings.py
 |    |-- urls.py
 |    |-- wsgi.py
 +-- manage.py

В качестве названия команды, вызываемой в команднйо строке используется имя файла. В нашем примере файл my_custom_command.py, так что команда будет вызываться следующим образом:

python manage.py my_custom_command

Основной пример

Создадим базовый пример своей команды:

management/commands/what_time_is_it.py

from django.core.management.base import BaseCommand
from django.utils import timezone

class Command(BaseCommand):
    help = 'Displays current time'

    def handle(self, *args, **kwargs):
        time = timezone.now().strftime('%X')
        self.stdout.write("It's now %s" % time)

В основном команда управления Django состоит из класса с именем Command, который наследуется от BaseCommand. Код команды должен быть определен внутри метода handle().

Посмотрите, мы назвали наш модуль what_time_is_it.py. Эта команда может быть выполнена как:

python manage.py what_time_is_it

Вывод исполнения команды:

It's now 18:35:31

Возникает вопрос, чем это отличается от обычного скрипта Python или в чем его польза. Наилучшим преимуществом является то, что все возможности Django доступны и готовы к использованию, что можно импортировать модели, выполнять запросы в базу данных с помощью 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 = u'Создание случайного пользователя'

    def add_arguments(self, parser):
        parser.add_argument('total', type=int, help=u'Количество создаваемых пользователей')

    def handle(self, *args, **kwargs):
        total = kwargs['total']
        for i in range(total):
            User.objects.create_user(username=get_random_string(), email='', password='123')

Использование этой команды:

python manage.py create_users 10

Опциональные аргументы

Опциональные (и именованные) аргументы могут передаваться в любом порядке. В следующем примере находится определение аргумента prefix, который будет скомпонован с полем username.

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='Количество создаваемых пользователей')

        # Optional argument
        parser.add_argument('-p', '--prefix', type=str, help='Префикс для username', )

    def handle(self, *args, **kwargs):
        total = kwargs['total']
        prefix = kwargs['prefix']

        for i in range(total):
            if prefix:
                username = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
            else:
                username = get_random_string()
            User.objects.create_user(username=username, email='', password='123')

Использование:

python manage.py create_users 10 --prefix custom_user

или

python manage.py create_users 10 -p custom_user

Если указан префикс, то поле username будет, например, таким: custom_user_oYwoxtt4vNHR. Без указания префикса результат будет следующим: oYwoxtt4vNHR, т. е. просто случайная строка.

Флаговые аргументы

Один из типов необязательных аргументов — это флаг, который использует булевый тип. Давайте добавим флаг — 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 = 'Create random users'

    def add_arguments(self, parser):
        parser.add_argument('total', type=int, help='Количество создаваемых пользователей')
        parser.add_argument('-p', '--prefix', type=str, help='Префикс для username')
        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 = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
            else:
                username = get_random_string()

            if admin:
                User.objects.create_superuser(username=username, email='', password='123')
            else:
                User.objects.create_user(username=username, email='', password='123')

Использование:

python manage.py create_users 2 --admin

Или

python manage.py create_users 2 -a

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

Давайте создадим новую команду delete_users. В ней мы передадим список идентификаторов пользователей, которых команда должна удалить из базы данных.

management/commands/delete_users.py

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Delete users'

    def add_arguments(self, parser):
        parser.add_argument('user_id', nargs='+', type=int, help='User 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(u'Пользователь"%s (%s)" удален успешно!' % (user.username, user_id))
            except User.DoesNotExist:
                self.stdout.write(u'Пользователь с id "%s" не существует.' % user_id)

Использование:

python manage.py delete_users 1

Результат:

Пользователь "SMl5ISqAsIS8 (1)" удален успешно!

Также мы можем передать список id, разделенных пробелом, таким образом команда удалит пользователей за один вызов:

python manage.py delete_users 1 2 3 4

Результат:

Пользователь с id "1" не существует.
Пользователь "9teHR4Y7Bz4q (2)" удален успешно!
Пользователь "ABdSgmBtfO2t (3)" удален успешно!
Пользователь "BsDxOO8Uxgvo (4)" удален успешно!

Стилизация

Можно улучшить предыдущий пример, добавив цвета для вывода сообщений:

management/commands/delete_users.py

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Delete users'

    def add_arguments(self, parser):
        parser.add_argument('user_id', nargs='+', type=int, help='User 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(u'Пользователь "%s (%s)" удален успешно!' % (user.username, user_id)))
            except User.DoesNotExist:
                self.stdout.write(self.style.WARNING(u'Пользователь с id "%s" не существует.' % user_id))

Использование:

python manage.py delete_users 3 4 5 6

Результат:

Ниже предоставлен список всех доступных стилей в виде пользовательской команды:

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Show all available styles'

    def handle(self, *args, **kwargs):
        self.stdout.write(self.style.ERROR('error - A major error.'))
        self.stdout.write(self.style.NOTICE('notice - A minor error.'))
        self.stdout.write(self.style.SUCCESS('success - A success.'))
        self.stdout.write(self.style.WARNING('warning - A warning.'))
        self.stdout.write(self.style.SQL_FIELD('sql_field - The name of a model field in SQL.'))
        self.stdout.write(self.style.SQL_COLTYPE('sql_coltype - The type of a model field in SQL.'))
        self.stdout.write(self.style.SQL_KEYWORD('sql_keyword - An SQL keyword.'))
        self.stdout.write(self.style.SQL_TABLE('sql_table - The name of a model in SQL.'))
        self.stdout.write(self.style.HTTP_INFO('http_info - A 1XX HTTP Informational server response.'))
        self.stdout.write(self.style.HTTP_SUCCESS('http_success - A 2XX HTTP Success server response.'))
        self.stdout.write(self.style.HTTP_NOT_MODIFIED('http_not_modified - A 304 HTTP Not Modified server response.'))
        self.stdout.write(self.style.HTTP_REDIRECT('http_redirect - A 3XX HTTP Redirect server response other than 304.'))
        self.stdout.write(self.style.HTTP_NOT_FOUND('http_not_found - A 404 HTTP Not Found server response.'))
        self.stdout.write(self.style.HTTP_BAD_REQUEST('http_bad_request - A 4XX HTTP Bad Request server response other than 404.'))
        self.stdout.write(self.style.HTTP_SERVER_ERROR('http_server_error - A 5XX HTTP Server Error response.'))
        self.stdout.write(self.style.MIGRATE_HEADING('migrate_heading - A heading in a migrations management command.'))
        self.stdout.write(self.style.MIGRATE_LABEL('migrate_label - A migration name.'))

Задания для утилиты cron

Если у вас есть задачи, которые должны периодически выполняться, можно использовать свою команду и добавить ее в crontab:

# m h  dom mon dow   command
0 4 * * * /home/mysite/venv/bin/python /home/mysite/mysite/manage.py my_custom_command

Этот пример будет выполнять каждый день в 4 часа команду my_custom_command.

Вернуться на верх