Writing custom django-admin commands

Проект может быть расширен собственными командами для manage.py. Например, вы можете добавить действие для развёртывания. Здесь же мы будем реализовывать действие closepoll для приложения polls из tutorial.

To do this, add a management/commands directory to the application. Django will register a manage.py command for each Python module in that directory whose name doesn’t begin with an underscore. For example:

polls/
    __init__.py
    models.py
    management/
        commands/
            _private.py
            closepoll.py
    tests.py
    views.py

В этом примере команда closepoll будет доступна для любого проекта, который импортирует приложение polls в INSTALED_APPS.

Модуль _private.py не доступен как команда для manage.py.

Для модуля closepoll.py должно быть соблюдено лишь одно требование - наличие в нём класса Command, который унаследован от BaseCommand или его потомков.

Автономные скрипты

Собственные команды могут быть полезны для реализации отдельных скриптов, например, для планировщика Windows или crontab.

Для реализации команды отредактируйте polls/management/commands/closepoll.py следующим образом:

from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll

class Command(BaseCommand):
    help = 'Closes the specified poll for voting'

    def add_arguments(self, parser):
        parser.add_argument('poll_ids', nargs='+', type=int)

    def handle(self, *args, **options):
        for poll_id in options['poll_ids']:
            try:
                poll = Poll.objects.get(pk=poll_id)
            except Poll.DoesNotExist:
                raise CommandError('Poll "%s" does not exist' % poll_id)

            poll.opened = False
            poll.save()

            self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))

Примечание

При реализации команды вы должны использовать self.stdout и self.stderr вместо stdout и stderr. Использование такого перенаправления может помочь при тестировании скрипта. Кстати, стоит помнить, что символ перевода строки ставится по умолчанию, если вы не указали другой в параметре ending:

self.stdout.write("Unterminated line", ending='')

The new custom command can be called using python manage.py closepoll <poll_ids>.

The handle() method takes one or more poll_ids and sets poll.opened to False for each one. If the user referenced any nonexistent polls, a CommandError is raised. The poll.opened attribute does not exist in the tutorial and was added to polls.models.Question for this example.

Получение необязательных аргументов

Аналогичный closepoll может быть легко изменён на удаление указанного голосования вместо его закрытия, для этого надо указать дополнительные аргументы при запуске команды. Такие дополнительные опции можно добавить в метод add_arguments() примерно так:

class Command(BaseCommand):
    def add_arguments(self, parser):
        # Positional arguments
        parser.add_argument('poll_ids', nargs='+', type=int)

        # Named (optional) arguments
        parser.add_argument(
            '--delete',
            action='store_true',
            help='Delete poll instead of closing it',
        )

    def handle(self, *args, **options):
        # ...
        if options['delete']:
            poll.delete()
        # ...

Параметр (delete в нашем случае) доступен в словаре параметров метода handle(). Подробнее можно посмотреть в документации по argparse, где говорится про использование add_argument.

In addition to being able to add custom command line options, all management commands can accept some default options such as --verbosity and --traceback.

Команды и локализация

By default, management commands are executed with the current active locale.

If, for some reason, your custom management command must run without an active locale (for example, to prevent translated content from being inserted into the database), deactivate translations using the @no_translations decorator on your handle() method:

from django.core.management.base import BaseCommand, no_translations

class Command(BaseCommand):
    ...

    @no_translations
    def handle(self, *args, **options):
        ...

Since translation deactivation requires access to configured settings, the decorator can’t be used for commands that work without configured settings.

Тестирование

Информация, о том как тестировать команды управления, может быть найдена в соответствующей документации.

Overriding commands

Django registers the built-in commands and then searches for commands in INSTALLED_APPS in reverse. During the search, if a command name duplicates an already registered command, the newly discovered command overrides the first.

In other words, to override a command, the new command must have the same name and its app must be before the overridden command’s app in INSTALLED_APPS.

Management commands from third-party apps that have been unintentionally overridden can be made available under a new name by creating a new command in one of your project’s apps (ordered before the third-party app in INSTALLED_APPS) which imports the Command of the overridden command.

Объект команды

class BaseCommand

Базовый класс для всех команд.

Используйте этот класс, если хотите добраться до всех механизмов, которые занимаются разбором аргументов командной строки и возвратом значения. Если вам не нужно вносить изменения в поведение, используйте один из его подклассов.

Наследование от BaseCommand подразумевает, что будет реализован метод handle().

Атрибуты

Все перечисленные атрибуты могут использоваться в производных от BaseCommand классах.

BaseCommand.help

Краткое описание команды, которое будет выведено в справочном сообщении при выполнении python manage.py help <command>.

BaseCommand.missing_args_message

Если ваша команда определяет обязательные позиционные аргументы, вы можете настроить возвращаемое сообщение об ошибке, возвращаемое в случае пропуска аргументов. По умолчанию, возвращается ответ от argparse («too few arguments»).

BaseCommand.output_transaction

Булево значение, которое определяет будет ли команда выводить SQL выражения. Если установлено в True, то весь вывод будет автоматически заключён между BEGIN; и COMMIT;. Значение по умолчанию - False.

BaseCommand.requires_migrations_checks

A boolean; if True, the command prints a warning if the set of migrations on disk don’t match the migrations in the database. A warning doesn’t prevent the command from executing. Default value is False.

BaseCommand.requires_system_checks

При True Django выполнит проверку всего проекта на наличие проблем перед выполнением команды. По умолчанию True.

BaseCommand.style

Атрибут экземпляра команды, который помогает выделять цветом вывод в stdout или stderr. Например:

self.stdout.write(self.style.SUCCESS('...'))

Смотрите Подсветка синтаксиса, чтобы узнать доступные стили и как поменять цвета (используйте названия «ролей», описанных в этом разделе, в верхнем регистре).

If you pass the --no-color option when running your command, all self.style() calls will return the original string uncolored.

Методы

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

Переопределение конструктора

Если вы переопределяете __init__, убедитесь, что вызываете в нём __init__ базового класса BaseCommand:

class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ...
BaseCommand.create_parser(prog_name, subcommand, **kwargs)

Returns a CommandParser instance, which is an ArgumentParser subclass with a few customizations for Django.

You can customize the instance by overriding this method and calling super() with kwargs of ArgumentParser parameters.

Changed in Django 2.2:

kwargs was added.

BaseCommand.add_arguments(parser)

Точка входа для добавления аргументов парсера для обработки аргументов командной строки. Команды должны переопределять этот метод для добавления как позиционных, так и необязательных аргументов, принимаемых командой. Вызов super() не требуется при прямом наследовании BaseCommand.

BaseCommand.get_version()

Возвращает версию Django, которая необходима для работы всех встроенных команд Django. Пользовательские команды могут переопределить этот метод и вернуть свою версию.

BaseCommand.execute(*args, **options)

Tries to execute this command, performing system checks if needed (as controlled by the requires_system_checks attribute). If the command raises a CommandError, it’s intercepted and printed to stderr.

Вызов команды в коде

execute() should not be called directly from your code to execute a command. Use call_command() instead.

BaseCommand.handle(*args, **options)

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

It may return a string which will be printed to stdout (wrapped by BEGIN; and COMMIT; if output_transaction is True).

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False)

Uses the system check framework to inspect the entire Django project for potential problems. Serious problems are raised as a CommandError; warnings are output to stderr; minor notifications are output to stdout.

Если app_configs и tags равны None, выполняется полная проверка. tags может содержкать список тегов, указывающих что проверять, например, compatibility или models.

BaseCommand subclasses

class AppCommand

Служебная команда, которая принимает в качестве аргументов имена установленных приложений и выполняет с ними однотипные действия.

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

AppCommand.handle_app_config(app_config, **options)

Выполняет действия для app_config, который является объектом AppConfig приложения, которое было указано при выполнении команды.

class LabelCommand

Команда, которая принимает один или несколько аргументов в командной строке (меток) и что-то делает с ними.

Вместо реализации handle() нужно реализовать handle_label(), который будет вызываться для каждого аргумента.

LabelCommand.label

A string describing the arbitrary arguments passed to the command. The string is used in the usage text and error messages of the command. Defaults to 'label'.

LabelCommand.handle_label(label, **options)

Выполняет действие для label, которая была передана через командную строку.

Исключение команды

exception CommandError

Этот класс сигнализирует о неожиданных ситуациях при выполнении команды.

Если это исключение произошло в ходе выполнения команды из консоли, то оно будет преобразовано в понятное сообщение об ошибке и выведено в стандартный поток ошибок (например, stderr). Генерация этого исключения (с подробным описанием ошибки) - предпочтительный способ сообщения об исключительной ситуации в ходе выполнения команды.

If a management command is called from code through call_command(), it’s up to you to catch the exception when needed.