Реализация собственных команд django-admin

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

Для этого добавим в приложение каталог management/commands. Для каждого модуля в этом каталоге, который не начинается с подчёркивания, Django создаст соответствующую команду. Например:

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

В Python 2 не забудьте добавить файлы __init__.py в каталоги management и management/commands, иначе ваша команда не будет найдена.

В этом примере команда 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 Poll

class Command(BaseCommand):
    args = '<poll_id poll_id ...>'
    help = 'Closes the specified poll for voting'

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

            poll.opened = False
            poll.save()

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

Примечание

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

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

Новая команда может быть запущена следующим образом: python manage.py closepoll <poll_id>.

Метод handle() принимает 0 или более poll_ids и устанавливает каждому poll.opened в False. Если пользователь запросит несуществующий опрос, то возникнет исключение CommandError. Атрибут poll.opened не существует в tutorial и добавлен в polls.models.Poll в этом примере.

Если же вместо закрытия опроса нам понадобиться удалить какой-либо из них, то это можно сделать с помощью списка параметров option_list, например, так:

from optparse import make_option

class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--delete',
            action='store_true',
            dest='delete',
            default=False,
            help='Delete poll instead of closing it'),
        )

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

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

Помимо возможности принимать пользовательские опции, все стандартные команды принимают также опции по умолчанию, такие как --verbosity и --traceback.

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

По умолчанию BaseCommand.execute() жёстко задаёт локаль как en-us, потому что встроенные команды, которые выполняют различные задачи(например, наполнение базы данных, генерация контента), требуют нейтральной кодировки (мы используем en-us).

Если пользовательская команда требует другой локали, то вы можете вручную активировать её в handle() или handle_noargs(), используя функции по работе с интернационализацией (I18N):

from django.core.management.base import BaseCommand, CommandError
from django.utils import translation

class Command(BaseCommand):
    ...
    can_import_settings = True

    def handle(self, *args, **options):

        # Activate a fixed locale, e.g. Russian
        translation.activate('ru')

        # Or you can activate the LANGUAGE_CODE # chosen in the settings:
        #
        #from django.conf import settings
        #translation.activate(settings.LANGUAGE_CODE)

        # Your command logic here
        # ...

        translation.deactivate()

В некоторых случаях необходимо использовать локаль с настроек и запретить Django устанавливать ‘en-us’. Это можно сделать, установив параметр BaseCommand.leave_locale_alone.

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

  • Убедитесь, что параметр USE_I18N установлен в True, когда команда запускается (это очень хороший пример потенциальных ошибок при использовании динамического окружения, поэтому лучше использовать фиксированные настройки).

  • Убедитесь в работоспособности команды при разных локалях.

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

class BaseCommand

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

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

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

Атрибуты

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

BaseCommand.args

Строка со списком аргументов, которые принимает команда. Может использоваться в справочных сообщениях, например, команда, которая принимает список имён приложений может установить значение ‘<app_label app_label ...>’.

BaseCommand.can_import_settings

Булево значение, которое определяет нужно ли команде импортировать настройки Django. Если установлено в True, то метод execute() проверит возможность импорта. Значение по умолчанию - True.

BaseCommand.help

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

BaseCommand.option_list

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

BaseCommand.output_transaction

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

BaseCommand.requires_system_checks
Добавлено в Django 1.7:

Булево. При True, перед запуском команды Django выполнит проверку проекта на предмет ошибок. Если requires_system_checks не указан, используется значение requires_model_validation. Если этот атрибут так же не указан, используется значение по умолчанию (True). При указании requires_system_checks и requires_model_validation будет вызвана ошибка.

BaseCommand.requires_model_validation

Не рекомендуется, начиная с версии 1.7: Заменен на requires_system_checks

Булево значение; если установлено в True, то проверка установленных моделей будет выполнена до выполнения команды. Значение по умолчанию True. Для валидации определённой модели приложения вместо всех вызывайте validate() из BaseCommand.handle().

BaseCommand.leave_locale_alone

Булево значение; указывает использовать ли локаль с настроек проекта, или ‘en-us’.

По умолчанию False.

Вы должны быть уверены в своих действиях при изменении этого параметра, если ваша команда меняет данные в базе данных, которые не должны зависеть от локали и содержать переведенный текст (например, при создании прав доступа в приложении django.contrib.auth). Указание локали, отличную от стандартной ‘en-us’, может привести к неожиданным эффектам. Смотрите Команды и локализация.

Этот параметр не может быть False, если can_import_settings равна False т.к. для установки локаль необходим доступ к настройкам. В таком случае будет вызвано исключение CommandError.

Добавлено в Django 1.6:

Опция leave_locale_alone была добавлена в Django 1.6.

Методы

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

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

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

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

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

BaseCommand.execute(*args, **options)

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

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

Не вызывайте execute() непосредственно из кода команды. Используйте вместо этого call_command.

BaseCommand.handle(*args, **options)

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

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False)
Добавлено в Django 1.7:

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

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

BaseCommand.validate(app=None, display_num_errors=False)

Не рекомендуется, начиная с версии 1.7: Заменено на команду check

Если app равно None, то будут проверены все приложения.

Подклассы BaseCommand

class AppCommand

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

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

AppCommand.handle_app_config(app_config, **options)

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

Изменено в Django 1.7:

В предыдущих версиях команда, унаследованная от:class:AppCommand, должна была реализовать метод handle_app(app, **options), где app был модуль с моделями. Новый API позволяет работать с приложениями без моделей. Самый простой способ обновить команды:

def handle_app_config(app_config, **options):
    if app_config.models_module is None:
        return                                  # Or raise an exception.
    app = app_config.models_module
    # Copy the implementation of handle_app(app_config, **options) here.

Однако, лучше использовать атрибуты объекта app_config.

class LabelCommand

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

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

LabelCommand.handle_label(label, **options)

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

class NoArgsCommand

Команда, которая не принимает аргументов.

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

NoArgsCommand.handle_noargs(**options)

Просто напишите код здесь.

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

class CommandError

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

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

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