Djangoでカスタムコマンドを作る【Python】

154, 2021-01-08

目次

Djangoでカスタムコマンドを作る

Djangoのmanage.pyにコマンドを追加したい場合は、Djangoのカスタムコマンドを作成します。
カスタムコマンドを作成することでmanage.pyから使えるコマンドを増やすことが出来ます。
この記事ではこのカスタムコマンドの作り方を解説します。

具体的には↓を見ていきます。

  • カスタムコマンドの概要
  • プロジェクトを作る
  • アプリを作る
  • コマンドを作る
  • コマンドを実行する
  • コマンドの詳細について

カスタムコマンドの概要

Djangoのカスタムコマンドとは正確には「custom django-admin commands」のことを指します。

Djangoではアプリごとにコマンドを作ることが出来ます。
このコマンドはmanage.pyから実行することが出来ます。
たとえばshowpostsというコマンドを作った場合、↓のようにmanage.pyで実行することが可能です。

python manage.py showposts

Djangoで管理用にモデルを操作したり参照したりしたい場合に、ビューなどをわざわざ定義してブラウザから実行するのは非常に手間がかかり、めんどくさいことです。
しかし、カスタムコマンドを作ることでコマンドラインからモデル、ひいてはプロジェクト全体を参照することが可能になり、非常に便利になります。
カスタムコマンドを作りcronなどのスケジューラ―と組み合わせれば、定期的にプロジェクトに変更を加えるシステムも構築することが出来ます。

Djangoのカスタムコマンドはインストールしたアプリ以下にmanagementというディレクトリを作り、その下にcommandsというディレクトリを作ります。そしてコマンド名に相当するモジュールを配置し、モジュールの中でコマンドのクラスを作ることでDjangoがそのコマンドを認識します。
たとえばbbsというアプリを作り、その下にコマンドを配置する場合は↓のようなディレクトリ構造になります。

./bbs
├── admin.py
├── apps.py
├── __init__.py
├── management            # <-- これがコマンドを管理するディレクトリ
│   └── commands          # <-- これがコマンドを配置するディレクトリ
│       ├── __init__.py
│       └── showposts.py  # <-- これがコマンド本体。ファイル名がコマンド名になる
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

↑のようにshowpostsというコマンドを作った場合、manage.pyを実行すると↓のようにコマンドが表示されます。

$ python manage.py

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

Available subcommands:

...

[bbs]
    showposts

...

プロジェクトを作る

今回の解説のためにプロジェクトを作ります。
掲示板を作るという想定で、プロジェクト名は「mybbs」とします。

$ django-admin startproject mybbs

アプリを作る

続いてアプリを作成します。
これも掲示板アプリという想定でアプリ名は「bbs」とします。

$ python manage.py startapp bbs

アプリのカスタムコマンドをDjangoに認識させるには、アプリをプロジェクトにインストールしておく必要があります。
プロジェクトのsettings.pyINSTALLED_APPSを編集して忘れずにアプリをインストールしておくようにします。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bbs',
]

モデルを作る

今回はテスト用にPostというモデルを作ります。
これはbbsアプリの掲示板の投稿内容を表すモデルです。

from django.db import models


class Post(models.Model):
    name = models.CharField(max_length=128, help_text='投稿者名')
    content = models.TextField(max_length=1024, help_text='投稿内容')

モデルを定義したら忘れずにマイグレーションを済ませておきます。

$ python manage.py makemigrations
$ python manage.py migrate

コマンドを作る

アプリのコマンドを実際に作っていきます。
bbsアプリ以下にmanagementディレクトリを作り、その下にcommandsディレクトリを作ります。

$ cd bbs
$ mkdir management
$ mkdir management/commands

commandsディレクトリ以下にshowposts.pyというファイルを作ります。
このshowpostsというファイル名がそのままコマンド名になります。
今回は掲示板の投稿(Post)を一覧表示するコマンドという想定です。

showposts.pyは↓のように書いておきます。

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


class Command(BaseCommand):
    help = 'Show post list'

    def handle(self, *args, **options):
        self.stdout.write('show posts')

↑のようにモジュール内にCommandというクラスを作ります。
このクラスはBaseCommandを継承するようにします。
そしてhelp属性にコマンドの説明を書いておきます。
handle()メソッドをオーバーライドして、コマンドが実行されたときの処理を書きます。
↑の場合は標準出力に「show posts」と出力しています。

スポンサーリンク

コマンドを実行する

コマンドを作成したので↓のようにコマンドを実行します。

$ python manage.py showposts
show posts

すると↑のように「show posts」と出力されます。
-hオプションを指定して実行するとコマンドの使い方が表示されます。

$ python manage.py showposts -h
usage: manage.py showposts [-h] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH]
                           [--traceback] [--no-color] [--force-color] [--skip-checks]

Show post list

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.
  --force-color         Force colorization of the command output.
  --skip-checks         Skip system checks.

コマンドの詳細について

ここからコマンドの詳細について見ていきます。

標準出力と標準エラーについて

コマンドで標準出力と標準エラーを使うにはそれぞれself.stdoutself.stderrを使うのが推奨されています。
これらのファイルオブジェクトを使うことでコマンドのテストやデバッグがしやすくなります。

    def handle(self, *args, **options):
        self.stdout.write('success')
        self.stderr.write('error')

また、これらのオブジェクトのwrite()メソッドは改行を自動で付加します。そのため開発者が行末に改行を付加する必要はありません。
改行を取り除きたい場合はending引数に空文字列を指定します。

    def handle(self, *args, **options):
        self.stdout.write('success', ending='')
        self.stderr.write('error', ending='')

引数の追加

コマンドはデフォルトでは引数を取りません。
しかしコマンドが引数を取るようにしたい場合があります。
たとえばcreatepostというコマンドからPostを作成する場合、コマンドラインからその属性値を指定できるようにした方が便利です。
↓のようにです。

$ python manage.py createpost postname postcontent

そういう場合は↓のようにします。

from django.core.management.base import BaseCommand, CommandError
from bbs.models import Post


class Command(BaseCommand):
    help = 'Create post'

    def add_arguments(self, parser):
        parser.add_argument('attrs', nargs='+', type=str)

    def handle(self, *args, **options):
        attrs = options['attrs']
        print(attrs)

        post = Post.objects.create(name=attrs[0], content=attrs[1])
        print(post.name, post.content)

↑のようにadd_arguments()というメソッドをオーバーライドして、parserに引数の定義を追加します。
parser.add_argument('attrs', nargs='+', type=str)は可変長の文字列の引数attrsを定義しています。

このcreatepostコマンドを実行すると↓のように出力されます。

['postname', 'postcontent']
postname postcontent

↑の出力を見るとoptionsに格納されているattrsは文字列の入ったリストであることがわかります。
また、そのリストの第1引数をname, 第2引数をcontentに設定してPostオブジェクトを作成し、結果が成功していることもわかります。

オプションの追加

コマンドにオプションを追加するには↓のようにadd_arguments()を編集します。

from django.core.management.base import BaseCommand, CommandError
from bbs.models import Post


class Command(BaseCommand):
    help = 'Create post'

    def add_arguments(self, parser):
        parser.add_argument('-n', '--name', required=True, type=str)
        parser.add_argument('-c', '--content', required=True, type=str)

    def handle(self, *args, **options):
        name = options['name']
        content = options['content']

        post = Post.objects.create(name=name, content=content)
        print(post.name, post.content)

↑の場合、コマンドにオプション-n--name, -c--contentを追加しています。
これらのオプションは必須(required=True)で、タイプは文字列(type=str)です。
-n--namePostnameの指定で、-c--contentPostcontentの指定です。

↑のコマンド(createpost)を実行すると↓のように出力されます。

$ python manage.py createpost --name postname --content postcontent
postname postcontent

これらの引数の設定はparserに対して行いますが、その他のオプションの設定方法についてはargparseモジュールを参照してください。

おわりに

今回はDjangoのカスタムコマンドの作り方を紹介しました。
カスタムコマンドを作れるようになるとDjangoを自動化することが出来るようになります。
押さえておきたいところですね。

困ったらカスタムコマンド

スポンサーリンク

投稿者名です。64字以内で入力してください。

必要な場合はEメールアドレスを入力してください(全体に公開されます)。

投稿する内容です。

スポンサーリンク

スポンサーリンク