カスタム django-admin コマンドの作り方

manage.py を用いることで独自のアクションを登録する事ができます。例として、あなたが配布している Django アプリケーションに manage.py アクションを追加したくなったとします。このドキュメントでは、このチュートリアル で作成した polls アプリケーションに独自の closepoll コマンドを追加します。

独自のコマンドを追加するためには、 management/commands ディレクトリをアプリケーションに追加してください。Djangoは、そのディレクトリ内のアンダーバーで始まらないPythonモジュールすべてを manage.py コマンドに登録します。例えば:

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

この例では、closepoll コマンドは polls アプリケーションを INSTALLED_APPS に含むプロジェクト全てで利用できるようになります。

_private.py モジュールは管理コマンドとして利用できません。

closepoll.py モジュールには一つだけ満たすべき要件があります。それは、 BaseCommand クラスもしくはその サブクラス の一つを継承した Command クラスを定義することです。

スタンドアロンのスクリプト

カスタム管理コマンドはスタンドアロンのスクリプト、 UNIX の crontab や Windows のタスクスケジューラ管理パネルから定期的に実行されるスクリプトを処理する場合に特に有用です。

コマンドを実装するには、polls/management/commands/closepoll.py を以下のように編集してください:

from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll


class Command(BaseCommand):
    help = "Closes the specified poll for voting"

    def add_arguments(self, parser):
        parser.add_argument("poll_ids", nargs="+", type=int)

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

            poll.opened = False
            poll.save()

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

注釈

管理コマンドを使用してコンソール出力を提供したい場合は、直接 stdoutstderr に出力するのではなく、self.stdoutself.stderr に書き込むべきです。これらのプロキシを使用することで、カスタムコマンドのテストがはるかに簡単になります。また、メッセージの終わりに改行文字を追加する必要はないことにも注意してください。ending パラメータを指定しない限り、改行文字は自動的に追加されます:

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

新たに作成したカスタムコマンドは python manage.py closepoll <poll_ids> と実行する事で利用できます。

handle() メソッドは一つ以上の poll_ids を受け取り、それぞれに対応した poll.openedFalse にセットします。もしコマンドの利用者が存在しない poll を指定した場合、CommandError 例外が発生します。poll.opened 属性は元の チュートリアル には存在しないので、この例では polls.models.Question モデルに追加しました。

省略可能な引数を受け入れる

上記の closepoll は、追加のコマンドラインオプションを受け入れることで、投票を閉じる代わりに削除するように簡単に変更できます。これらのカスタムオプションは、次のように add_arguments() メソッドで追加できます:

class Command(BaseCommand):
    def add_arguments(self, parser):
        # Positional arguments
        parser.add_argument("poll_ids", nargs="+", type=int)

        # Named (optional) arguments
        parser.add_argument(
            "--delete",
            action="store_true",
            help="Delete poll instead of closing it",
        )

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

オプション(例では delete)は handle メソッドで辞書型変数の引数として利用可能です。add_argument の利用についてより詳細な情報を得るには Python 公式ドキュメントの argparse を参照してください。

独自のコマンドラインオプションを追加できるのに加え、管理コマンド に定義された --verbosity--traceback といったオプションも標準で利用できます。

管理コマンドとロケール

デフォルトでは、管理コマンドは現在アクティブなロケールで実行されます。

何らかの理由でカスタム管理コマンドをアクティブなロケールなしで実行する必要がある (たとえば、翻訳されたコンテンツがデータベースに挿入されないようにしたい) 場合は、 handle() メソッドで @no_translations デコレータを使用して翻訳を無効にします。

from django.core.management.base import BaseCommand, no_translations


class Command(BaseCommand):
    ...

    @no_translations
    def handle(self, *args, **options): ...

翻訳の非アクティブ化には構成設定へのアクセスが必要なので、構成設定なしで機能するコマンドにデコレータを使用することはできません。

テスト

カスタム管理コマンドのテストに関する情報は テストに関するページ で得ることができます。

コマンドのオーバーライド

Djangoは、ビルトインコマンドを読み込んだ後に INSTALLED_APPS を逆順に検索してコマンドを登録します。この検索の際に、すでに登録済みのコマンド名と重複したコマンド名が見つかった場合、新しく見つかったコマンドで最初に見つけたコマンドをオーバーライドします。

別の言い方をすると、コマンドをオーバーライドするためには、新しいコマンドは、オーバーライドするコマンドと同じ名前でなければなりません。そして、そのアプリは、INSTALLED_APPS で、オーバーライドするコマンドのアプリよりも前にある必要があります。

意図せずオーバーライドされたサードパーティアプリからの管理コマンドは、オーバーライドされたコマンドの Command をインポートするプロジェクトのアプリ(INSTALLED_APPS でサードパーティアプリの前に注文) の1つで新しいコマンドを作成することにより、新しい名前で使用可能にできます。

Command オブジェクト

class BaseCommand

全ての管理コマンドの派生元となる基底クラス。

コマンドライン引数を解析し、それに対してどのコードを呼び出すかを決定するすべてのメカニズムにアクセスしたい場合は、このクラスを使用してください。そのような動作を変更する必要がない場合は、 サブクラス のいずれかを使用することを検討してください。

BaseCommand クラスのサブクラス化には handle() メソッドの実装が必要です。

属性

全ての属性は派生クラスでセットでき、BaseCommand クラスの サブクラス で利用可能です。

BaseCommand.help

コマンドに関する短い説明。ユーザーが python manage.py help <command> を実行することでヘルプメッセージとして表示されます。

BaseCommand.missing_args_message

コマンドが必須の位置引数を定義している場合、引数が足りない場合に返されるエラーメッセージをカスタマイズできます。デフォルトは argparse によって出力されます ("too few arguments")。

BaseCommand.output_transaction

コマンドが SQL 文を出力するかどうかを決めるブール値。True の場合、出力文が自動的に BEGIN;COMMIT; で囲まれます。デフォルトの値は False です。

BaseCommand.requires_migrations_checks

Boolean。True の場合、ディスク上に存在する一連のマイグレーション定義がデータベース上に保存されたマイグレーション定義とマッチしない場合に警告を出力します。この警告はコマンドの実行を停止させる物ではありません。デフォルトの値は False です。

BaseCommand.requires_system_checks

例えば [Tags.staticfiles, Tags.models] のようなタグのリストやタプル。 指定したタグに登録された システムチェックは、コマンドを実行する前にエラーがないかチェックされます。 '__all__' を使用することで、すべてのシステムチェックを実行するように指定することができます。デフォルトでは '__all__' に設定されています。

BaseCommand.style

stdoutstderr を記述した際にカラー出力を補助するインスタンス変数です。以下の利用例を参照ください:

self.stdout.write(self.style.SUCCESS("..."))

カラーパレットの調整と利用可能なスタイルについては シンタックスカラーリング を参照してください (このセクションに記述されている "roles" のアルファベットを大文字にすると利用できます)。

--no-color オプションを渡してコマンドを実行した場合、全ての self.style() 呼び出しはオリジナルのカラー分けされていない出力を行います。

BaseCommand.suppressed_base_arguments

ヘルプ出力で抑制するデフォルトのコマンドオプション。これは、オプション名のセットでなければなりません(例:'--verbosity')。抑制されたオプションのデフォルト値はそのまま渡されます。

メソッド

BaseCommand には、いくつかのオーバーライド可能なメソッドが含まれています。しかし、handle() メソッドだけは、実装する必要があります。

サブクラス内でのコンストラクタの実装

BaseCommand を継承したサブクラス内で __init__ を実装する場合、BaseCommand__init__ を呼び出す必要があります:

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

CommandParser インスタンスを返します。これは ArgumentParser サブクラスで、Django用にいくつかカスタマイズされています。

このメソッドをオーバーライドし、 ArgumentParser パラメータの kwargs を使って super() を呼び出すことでインスタンスをカスタマイズできます。

BaseCommand.add_arguments(parser)

コマンドに渡されたコマンドライン引数を操作するパーサーを追加するためのエントリポイントです。カスタム管理コマンドが受け取る位置引数およびオプション引数を追加するためにはこのメソッドをオーバーライドする必要があります。直接 BaseCommand を継承している場合は super() の呼び出しは必要ありません。

BaseCommand.get_version()

Djangoのバージョンを返します。これはすべての組み込みDjangoコマンドに対して正しいはずです。 ユーザー指定のコマンドは、このメソッドをオーバーライドして独自のバージョンを返すことができます。

BaseCommand.execute(*args, **options)

コマンドを実行し、必要とされた場合(requires_system_checks 属性によって設定可能)システムチェックを行います。コマンドが CommandError 例外を発生させた場合は、実行を中断して stderr に出力します。

コード中での管理コマンドの呼び出し

カスタム管理コマンドを実行するためにコード中から execute() を直接呼び出す事は避けてください。代わりに call_command() を利用してください。

BaseCommand.handle(*args, **options)

コマンドにおける実際の処理内容。サブクラスはこのメソッドを実装しなければなりません。

stdout に出力される文字列を返すことができます (output_transactionTrue の場合、文字列は BEGIN;COMMIT; で挟まれて出力されます)。

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False, include_deployment_checks=False, fail_level=checks.ERROR, databases=None)

潜在的な問題のために Django プロジェクト全体を検証するシステムチェックフレームワークを利用します。致命的な問題は CommandError 例外を発生し、警告は stderr への出力、重要でない通知は stdout への出力となります。

app_configs および tags が共に None であった場合、デプロイメントとデータベース関連のチェック以外の全てのシステムチェックが実行されます。tags はチェックタグ、例えば compatibility あるいは models 等、のリストとなります。

また、include_deployment_checks=True を渡すとデプロイメントのチェックを行い、databases にデータベースエイリアスのリストを渡すとデータベース関連のチェックを行います。

BaseCommand のサブクラス

class AppCommand

一つ以上のインストールされたアプリケーションラベルを引数として受け取り、それぞれに対して何らかの処理を行う管理コマンド。

handle() を実装する代わりに、サブクラスでは、アプリケーション毎に一度ずつだけ呼び出される handle_app_config() を実装する必要があります。

AppCommand.handle_app_config(app_config, **options)

コマンドラインで渡されたアプリケーションラベル個々に対応している AppConfig のインスタンスである app_config に応じたコマンドの処理を行います。

class LabelCommand

コマンドラインで1つ以上の任意の引数(ラベル)を受け取り、それぞれに対して何かを行う管理コマンド。

サブクラスは、handle() を実装するのではなく、ラベルごとに1回呼び出される handle_label() を実装する必要があります。

LabelCommand.label

コマンドに渡される任意引数について記述した文字列。この文字列はコマンドの使用法やエラーメッセージに利用します。デフォルトは 'label' です。

LabelCommand.handle_label(label, **options)

コマンドラインに渡された文字列である label に対応したコマンドの処理を行います。

コマンドが発生させる例外

exception CommandError(returncode=1)

管理コマンド実行中に発生した問題について示した例外クラス。

この例外がコマンドラインコンソールからの管理コマンドの実行中に発生した場合、キャッチされて適切な出力ストリーム(つまり、stderr)に対して整形されたエラーメッセージに変換されます。そのため、コマンドの実行中に何かが間違っていることを示す場合は、この例外を(エラーの理にかなった説明とともに)発生させることを推奨します。任意で returncode 引数を受け入れて、sys.exit() で管理コマンドが終了する際の終了ステータスをカスタマイズできます。

call_command() を介して管理コマンドが実行された場合は、例外の捕捉をするかどうかは実装に依存します。

Back to Top