[Python][Django] manage.pyにコマンドを追加する方法

概要

manage.pyに新しいコマンドを追加し、Djangoの環境内で任意の処理を行えるようにします。例えば、以下のようなことができるようになります。

$ PYTHONPATH=.. python manage.py hello
Hello

環境

この記事の内容は、Ubuntu Linux 6.10, Python 2.4.4c1, Django 0.97-pre-SVN-6843で確認しました。

やり方

概要に記したように、"Hello"を表示する"hello"コマンドを追加するとします。ここで、以下のように、projectディレクトリにappアプリケーションがあるものとします(settings.pyの設定は、完了しているとします)。また、カレントディレクトリはprojectディレクトリであるとします。

project
-- __init__.py
-- app
-- __init__.py
-- models.py
`-- views.py
-- manage.py
-- settings.py
`-- urls.py

まず、project.appパッケージの下に、management.commandsパッケージを追加します。すなわち、以下のようにディレクトリとファイルを作成します。

project
`-- app
    `-- management
       |-- __init__.py 
        `-- commands
            `-- __init__.py

新しく作成したふたつの__init__.pyは、空で構いません。

次に、app/management/commandsディレクトリに、<コマンド名>.pyというファイルを作成します。今回の例なら、hello.pyです。ひとつのコマンドにつき、ひとつのファイルが必要です。

app/management/commands/hello.pyには、django.core.management.base.BaseCommandクラスを継承したCommandクラスを記述します。このクラスの名前を、Command以外にすることはできません(このため、ひとつのファイルにはひとつのコマンドしか記述できません)。Commandクラスには、handleメソッドを記述します(以下)。

# -*- coding: utf-8 -*-

from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def handle(self, *args, **options):
        print "Hello"

# vim: tabstop=4 shiftwidth=4 expandtab softtabstop

以上により、manage.pyからhelloコマンドが使用できます。ただし、settings.pyのINSTALLED_APPSに設定されたproject.appパッケージをDjangoが見つけられるように、環境変数PYTHONPATHに親ディレクトリを設定して実行します。

$ PYTHONPATH=.. python manage.py hello
Hello

詳細

Commandクラスの継承元となるクラスは、BaseCommandクラスの他に、AppCommandクラスとLabelCommandクラス、NoArgsCommandクラスがあります。また、いくつかのクラス変数を記述することで、オプションを指定したり、ヘルプを記述したりすることができます。

BaseCommandクラス

BaseCommandを継承したクラスのhandleメソッドのargsとoptionsには、作成したコマンドに渡された引数とオプションが設定されます。また、handleメソッドがTrueと判定される値を返した場合、その値が標準出力に出力されます。

BaseCommand以外のクラスは、すべてのこのクラスを継承しています。

AppCommandクラス

アプリケーションごとになんらかの処理を行う場合、このクラスを継承します。コマンドは、

$ PYTHONPATH=.. python manage.py [オプション] <アプリケーション名> ...

のようにして実行します。

コマンドの処理内容は、handleメソッドではなく、handle_appメソッドに記述します。

    def handle_app(self, app, **options):
        pass

handle_appメソッドのapp引数には、コマンドラインで指定されたアプリケーションのmodelsモジュールが設定されます(もしmodelsモジュールがなかった場合、このコマンドは実行できません)。

django.core.management.commandsパッケージにあるファイルをみると、モデルからSQLを出力するコマンドが、このクラスを継承しているようです。

LabelCommandクラス

名前を指定してなんらかの処理を行う場合、このクラスを継承します。コマンドは、

$ PYTHONPATH=.. python manage.py [オプション] <名前> ...

のようにして実行します。

コマンドの処理内容は、handleメソッドではなく、handle_labelメソッドに記述します。

    def handle_label(self, label, **options):
        pass

handle_labelメソッドのlabel引数には、コマンドラインで指定された名前が設定されます。

django.core.management.commandsパッケージでは、startappコマンドやstartprojectコマンドが、このクラスを継承しています。

NoArgsCommandクラス

実行する際になんの引数も必要としない場合(オプションはあっても構いません)、このクラスを継承します。

コマンドの処理内容は、handleメソッドではなく、handle_noargsメソッドに記述します。

    def handle_noargs(self, **options):
        pass

このクラスを継承したコマンドに引数を与えると、例外が発生します。

クラス変数

argsクラス変数を設定すると、そのコマンドのヘルプに、設定した内容が追加されます。例えば、

class Command(BaseCommand):

    args = "foo"

とすると、

$ PYTHONPATH=.. python manage.py hello --help
(略)
usage: manage.py hello [options] foo
(略)

となります。

helpクラス変数には、そのコマンドの説明を記述できます。これは、--helpオプションでみることができます。

class Command(BaseCommand):

    help = u"Helloを表示します。"

とした場合、

$ PYTHONPATH=.. python manage.py hello --help
(略)
Helloを表示します。
(略)

となります。

option_listクラス変数には、コマンドが取り得るオプションを、optparse.make_option関数を使って指定します。このとき、継承元のクラスのオプションも表示されるように、親クラスのoption_list属性に追加して設定します。例えば、以下のようにします。

from optparse import make_option

class Command(BaseCommand):

    option_list = BaseCommand.option_list + (make_option("--foo", action="store", dest="foo_option", default="bar", help=u"fooオプション"), )

このとき、ヘルプに、

  --foo=FOO_OPTION      fooオプション

が表示され、handleメソッドのoptions引数のfoo_optionキー(make_option関数のdestパラメータで指定した名前です。オプションそのものの名前ではありません)に、オプションの値が設定されます。