Командовать парадом будет Django!

А вы задумывались как работает волшебный manage.py? Как он там внутри устроен? А, между прочим, очень интересно.

Вначале чуть-чуть истории. До августа прошлого года вся логика manage.py была в одном большом файле django/core/management.py. Там обработчики всех команд были написаны скопом, от чего файл стал просто монcтруозным и трудно поддавался созерцанию. Да и не расширяем извне.

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

Вся машинерия переехала в django/core/management/commands/. Каждая команда расположена в отдельном файле. Имя файла является именем команды и именно его нужно указывать первым параметром в manage.py.В этом файле должен быть объявлен класс Command в общем случае наследник от BaseCommand. В недрах которых скрывается логика обработки параметров командной строки ОС посредством стандартного модуля optparse.

Основным аспектом создания команды является реализация метода handle, который принимает позиционные параметры и именованные(опции). Он выглядит так:

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

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

  • AppCommand - для обработки конкретных приложений. Метод:

    def handle_app(self, app, **options):
        """
          app - объект приложения
        """
        #...
    
  • LabelCommand - для обработки меток.

    def handle_label(self, label, **options):
        """
          label - имя метки. Передаются как позиционные аргументы в командной строке
        """
        #...
    
  • NoArgsCommand - команды без аргументов, но может иметь опции

    def handle_noargs(self, **options):
        #...
    

BaseCommand имеет несколько стандартных опций, такие как --settings, --pythonpath и --traceback. В производных командах можно добавлять опции, добавляя атрибут option_list. Например как в стандартной команде test:

class Command( BaseCommand ):
    option_list = BaseCommand.option_list + (
         make_option('--verbosity', action='store', dest='verbosity', default='1',
             type='choice', choices=['0', '1', '2'],
             help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
         make_option('--noinput', action='store_false', dest='interactive', default=True,
             help='Tells Django to NOT prompt the user for input of any kind.'),
     )

Функция make_option - входит в пакет optparse.

Также команда может иметь атрибут requires_model_validation, если он установлен в True, то перед запуском команды будет проведена валидация моделей.

Ещё два полезных атрибута, это help и args, в которых можно описать вспомогательную информацию и подсказку по использованию текущей команды.

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

Для примера покажу реализацию команды которая печатает имена моделей в заданном приложении и если установлена опция --count то и количество объектов данной модели в базе данных:

#myapp.management.commands.modelsinfo.py

from django.core.management.base import AppCommand
from optparse import make_option

class Command( AppCommand ):
    option_list = AppCommand.option_list + (
        make_option('--count', action='store_true', dest='count', default= False,
            help='Add object count information' ),
    )
    help = 'Prints model names for given application and optional object count.'
    args = '[appname ...]'

    requires_model_validation = True

    def handle_app(self, app, **options):
        from django.db.models import get_models

        lines = []

        for model in get_models( app ):
            lines.append( "[%s]" % model.__name__ + ( options["count"] and " - %s objects" % model._default_manager.count() or "" ) )

        return "\n".join( lines )

Ну и вот такой результат получился у меня:

> ./manage.py modelsinfo auth --count
  [Message] - 0 objects
  [Group] - 0 objects
  [User] - 1 objects
  [Permission] - 165 objects

Этот пример кончено мало полезен, но зато показывает базовый принцип использования команд в джанго. А вообще довольно часто есть необходимость в таких служебных действиях, которые очень удобно делать и использовать как часть manage.py.

Так что пользуйтесь этим инструментом!:)

Комментарии 12

  1. Andy написал:

    Интересно, спасибо.

    Только текст читается с трудом, ибо:

    • "видимо" и "между прочим" следует выделять запятыми
    • "по средствам модуля" -> "посредством модуля"
    • слишком часто встречается слово "который"
    Оставлен 05 Февраль 2008 в 22:33
  2. Александр Кошелев написал:

    Спасибо за замечания. Учтем.

    Оставлен 06 Февраль 2008 в 00:42
  3. L0rda написал:

    Что я делаю не так?:)

    $ python manage.py modelsinfo auth --count Unknown command: 'modelsinfo' Type 'manage.py help' for usage.

    поставил deseb, смотрю как там все сделано - вроде все тоже самое, но там работает :)

    приложение прописано в INSTALLED_APPS.

    Оставлен 10 Февраль 2008 в 22:49
  4. Александр Кошелев написал:

    Интересная ситуация... __init__.py нигде не забыл?:) У меня так иногда случается...

    Оставлен 10 Февраль 2008 в 22:53
  5. L0rda написал:

    Все на месте 8)

    
           $ ls -la nagios/__init__.py
    -rw-r--r-- 1 l0rdadev l0rdadev 31 Feb 10 19:28 nagios/__init__.py
    
    $ ls -la nagios/management/__init__.py
    -rw-r--r-- 1 l0rdadev l0rdadev 0 Feb 10 19:16 nagios/management/__init__.py
    
    $ ls -la nagios/management/commands/
    total 16
    drwxr-xr-x 2 l0rdadev l0rdadev 4096 Feb 10 19:40 .
    drwxr-xr-x 3 l0rdadev l0rdadev 4096 Feb 10 19:16 ..
    -rw-r--r-- 1 l0rdadev l0rdadev    0 Feb 10 19:17 __init__.py
    -rw-r--r-- 1 l0rdadev l0rdadev  882 Feb 10 19:24 makeconfig.py
    -rw-r--r-- 1 l0rdadev l0rdadev  765 Feb 10 19:40 modelsinfo.py
          
    

    уже час сижу торможу :)

    Оставлен 10 Февраль 2008 в 23:03
  6. Александр Кошелев написал:

    Мдя... Рассказываю животрепещущая историю: я обычно в INSTALLED_APPS и вообще везде где можно, пишу полный import path, т.е. всегда в начало добавляю имя проекта, например myproject.myapp.models и т.п. Ну так вот, когда идет загрузка доступных команд, пакет myproject не доступен и надо писать myapp.models. Это я условно. Т.е. родительский для проекта каталог не находится в PYTHON_PATH. Может у тебя этот случай?

    Оставлен 10 Февраль 2008 в 23:23
  7. L0rda написал:

    точно, именно этот случай, спасибо, заработало :)

    Оставлен 10 Февраль 2008 в 23:28
  8. Александр Кошелев написал:

    Ура! Отлично:)

    Оставлен 10 Февраль 2008 в 23:31
  9. Vitaliy написал:

    Спасибо оказалось очень полезно (а то я уже начал делать велосипед для однго проекта в котором для некоторых приложений нужно запускать задачи в cron`e)

    Оставлен 15 Февраль 2008 в 02:55
  10. karantir написал:

    Немного оффтопа... У вас очень симпатичная подсветка питоновского кода. Под чем вы кодите? Не поделитесь цветовой схемой?

    Оставлен 27 Февраль 2008 в 13:20
  11. Александр Кошелев написал:

    Ничего особенного, для подсветки в блоге использую pygments c таким стилем.

    Кстати, в текстовых редакторах предпочитаю классическую схему - светлый фон, темный текст.

    Оставлен 29 Февраль 2008 в 00:13
  12. gearheart написал:

    так как в результате решать вопрос с тем что родительский проект не в python_path? если написать в settings.py

    INSTALLEDAPPS = ( .... 'appname', )

    то работает manage.py но не работает админка "ImportError: No module named app_name"

    Оставлен 04 Август 2008 в 14:01

Пингбеки 4

  1. От [newbie] помогите въехать в идеологию django на конкретной задаче 31 Январь 2009 в 06:09

    [...]Оформить его в виде команды Джанго. Тогда в скрипте сразу будет нужная среда.[...]

  2. От Маниакальный Веблог » Антиспам и сеть белых списков 28 Апрель 2008 в 04:48

    [...]ако для вящего удобства тех, кто захочет Cicero использовать, я оформил скрипт обновления в виде джанговской команды, вызов которой в кроне выглядит примерно так:[...]

  3. От Serge Matveenko » links for 2008-02-05 05 Февраль 2008 в 22:18

    [...]ÐŸÐ¾Ñ Ñ‚ “Командовать парадом будет Django!” | Интернет нового века | webnewage.org[...]

  4. От Маниакальный Веблог » Собственные команды для manage.py 05 Февраль 2008 в 10:27

    [...]Александр Кошелев полно и подробно пишет про описание пользовательских команд для manage.py. Одна из приятных фичек в Джанге последнего времени.[...]