Djangoのカスタムコマンドを作成してコマンドラインから実行する

# Djangoのカスタムコマンドを作成してコマンドラインから処理を実行する

## はじめに

Webアプリケーションの開発を行っていると、コマンドラインから特定の処理（集計処理、API呼び出し、メール配信など）を実行したい場合があります。この記事ではDjangoアプリケーションをコマンドラインから実行する処理の書き方を紹介します。

## Djangoのコマンドラインツール

Djangoにはデフォルトでコマンドラインツールが付属しており、例えば次のような処理をコマンドラインから実行することができます。

```shell
python manage.py startapp sample => アプリケーションの作成
python manage.py migrate         => DBマイグレーションの実行
python manage.py shell           => インタラクティブShellの起動
python manage.py runserver       => 開発サーバーの起動
```

この他にも様々なコマンドが用意されており`python manage.py`とタイプすると、使用できるコマンドの一覧を表示できます。

```shell
% python manage.py      

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
    changepassword
    createsuperuser

[debug_toolbar]
    debugsqlshell

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver

[sessions]
    clearsessions

[staticfiles]
    collectstatic
    findstatic
    runserver
```

より詳細に特定のコマンドのヘルプを参照するには`python manage.py help runserver`のように`help`に続けて参照したいコマンドの名称を入力します。

```shell
% python manage.py help runserver     
usage: manage.py runserver [-h] [--version] [-v {0,1,2,3}]
                           [--settings SETTINGS] [--pythonpath PYTHONPATH]
                           [--traceback] [--no-color] [--ipv6] [--nothreading]
                           [--noreload] [--nostatic] [--insecure]
                           [addrport]

Starts a lightweight Web server for development and also serves static files.

positional arguments:
  addrport              Optional port number, or ipaddr:port

optional arguments:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.
  --ipv6, -6            Tells Django to use an IPv6 address.
  --nothreading         Tells Django to NOT use threading.
  --noreload            Tells Django to NOT use the auto-reloader.
  --nostatic            Tells Django to NOT automatically serve static files
                        at STATIC_URL.
  --insecure            Allows serving static files even if DEBUG is False.
```

## Djangoのカスタムコマンドの作成

Djangoのコマンドラインツールには、コマンドを追加するための仕組みが用意されています。カスタムコマンドを追加すると、次のようにコマンドラインツールから実行することができます。

```shell
python manage.py <カスタムコマンド名>
```

## サンプル

今回はこちらの[ブログ管理アプリケーション](https://github.com/checkpoint/django_sample)にカスタムコマンドを追加し、実行してみましょう。最終的なソースコードはリンク先のGithubの内容を確認してください。

ブログ管理アプリケーションのblogディレクトリの構成は以下の通りです。

```shell
├── blog
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations/
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── django_sample/
├── manage.py
└── requirements.txt
```

### ディレクトリの作成

まずはblogアプリケーション配下に、コマンドを格納するためのディレクトリ（management/commands）を作成します。

```shell
mkdir -p blog/management/commands
touch blog/management/__init__.py
touch blog/management/commands/__init__.py  
```

### コマンドの作成

次に作成したディレクトリ（management/commands/）にコマンド本体となる`count_entry.py`を作成します。

```shell
vi blog/management/commands/count_entry.py
```

カスタムコマンドは`BaseCommand`クラスを継承してクラスを作成します。実際の処理本体は`handle`メソッド内に記載します。今回は引数で渡されたブログの記事数を表示するプログラムを作成します。

```count_entry.py
# -*- coding:utf-8 -*-


from django.core.management.base import BaseCommand

from ...models import Article


# BaseCommandを継承して作成
class Command(BaseCommand):
    # python manage.py help count_entryで表示されるメッセージ
    help = 'Display the number of blog articles'

    # コマンドライン引数を指定します。(argparseモジュール https://docs.python.org/2.7/library/argparse.html)
    # 今回はblog_idという名前で取得する。（引数は最低でも1個, int型）
    def add_arguments(self, parser):
        parser.add_argument('blog_id', nargs='+', type=int)

    # コマンドが実行された際に呼ばれるメソッド
    def handle(self, *args, **options):
        for blog_id in options['blog_id']:
            articles_count = Article.objects.filter(blog_id=blog_id).count()

            self.stdout.write(self.style.SUCCESS('Article count = "%s"' % articles_count))
```

最終的なディレクトリ構成は次の通りです。

```shell
blog
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── management => カスタムコマンドを格納するディレクトリ
│   ├── __init__.py
│   └── commands
│       ├── __init__.py
│       └── count_entry.py
├── migrations/
├── models.py
├── tests.py
├── urls.py
└── views.py
```

### カスタムコマンドの実行

作成したカスタムコマンドを実行してみましょう。はじめに`python manage.py`を実行して、カスタムコマンドが追加されているか確認します。

```
% python manage.py 

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
    changepassword
    createsuperuser

[blog]
    count_entry => 追加されている

[debug_toolbar]
    debugsqlshell

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver

[sessions]
    clearsessions

[staticfiles]
    collectstatic
    findstatic
    runserver
```

count_entryコマンドが追加されています。次に`python manage.py count_entry 1`と入力し、作成したカスタムコマンドを実行します。

```
% python manage.py count_entry 1
Article count = "2"
```

カスタムコマンドが実行され、ブログの記事数が表示されました。また通常のDjangoのコマンドと同様にhelpコマンドも使用することができます。作成されたコマンドは通常のスクリプトと同じように、cronなどを利用して定期的に実行することも可能です。

## 終わりに

今回はDjangoのコマンドラインツールから実行できるカスタムコマンドの作成方法をご紹介しました。cronなどで定期的に処理を実行したい場合など、独自のコマンドを作成するケースはたくさんあると思います。ぜひDjangoのカスタムコマンドを作成し、活用してみてはいかがでしょうか。それでは楽しいDjangoライフを！

## 参考サイト

* https://docs.djangoproject.com/en/1.10/howto/custom-management-commands/#django.core.management.BaseCommand.add_arguments