Реализация собственных команд 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

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

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

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

Standalone scripts

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

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

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_model_validation

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

BaseCommand.leave_locale_alone

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

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

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

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

New in Django 1.6:

The leave_locale_alone option was added in Django 1.6.

Методы

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

Implementing a constructor in a subclass

Если вы переопределяете __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.

Calling a management command in your code

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

BaseCommand.handle(*args, **options)

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

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

Проверяет данные приложения, генерирует исключение CommandError в случае ошибки.

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

Подклассы BaseCommand

class AppCommand

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

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

AppCommand.handle_app(app, **options)

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

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, то у вас будет шанс перехватить исключения.