Александр Кошелев
Александр Кошелев Python-разработчик

Командовать парадом будет 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.

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

comments powered by Disqus