【Django】カスタムコマンドを使用しcron(crontab)で登録・削除を自動化


投稿日 2019年10月11日 >> 更新日 2023年3月2日

今回はDjangoでカスタムコマンドを使えるようにし、Unix系(Linux)でよく使われるcron(crontab)で登録や削除の自動化をしたいと思います。

crontabは、cronとも呼ばれ、スケジュールマネージャの機能を使えます。

例えば、「この時間にpython3 manage.py runserverを起動したい!」など時間を指定してシステムを実行したり、無料SSL証明書を自動で発行させたりすることができます。

無料SSL証明書についてはこちら→「LetsEncryptの無料SSL証明書をCertbotを使って自動発行

WindowsではタスクスケジューラというのがGUIで提供されているそうです。

カスタムコマンドというのは、Django起動時で使われる「python3 manage.py コマンド」のことです。

「コマンド」として使えるように、アプリ+カスタムコマンド用のスクリプトファイルを作成していきます。

実はDjangoにはサードパーティ製の「Django-crontab(公式)」というのがPyPiで用意されていますが、海外のWebサイトなどを拝見するかぎり、最後の更新が2016年なので使わない方がいいとのことでした。

便利そうなソフトなので、今後のバージョンアップを待ちたいと思います。

まず簡単にTodoリストアプリを作成してから本題に入って行こうと思うので、アプリが不要な方は飛ばしてください。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.8
pip 9.0.1
使用ライブラリ ライセンス
Django==2.2.6 BSD

Todoアプリコピペ用コード

Djangoがインストールされているのを確認し、プロジェクトを作成します。


$ django-admin startproject project

プロジェクトが作成されたら、cdコマンドでprojectディレクトリに移動しアプリケーションを作成します。


$ cd project

/project$ python3 manage.py startapp todo

todoのディレクトリが作成されているのを確認したら、projectディレクトリ内のsettings.pyを編集します。

# project/project/settings.py

INSTALLED_APPS = [
    'todo.apps.TodoConfig',  ←アプリを追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

....
....
LANGUAGE_CODE = 'ja'  ←編集
TIME_ZONE = 'Asia/Tokyo'  ←編集

次にデータベースであるモデルフィールドを定義するために、todoディレクトリ内のmodels.pyを編集します。

# project/todo/models.py

from django.db import models


class Todo(models.Model):
    title = models.CharField('タイトル', max_length=50)
    created_at = models.DateTimeField('日付', auto_now_add=True)

    def __str__(self):
        return self.title

管理画面で表示されるようにtodoディレクトリ内のadmin.pyを編集します。

# project/todo/admin.py

from django.contrib import admin
from .models import Todo


admin.site.register(Todo)

ここでモデルフィールドをデータベースに適用させ、管理画面を使えるようにスーパーユーザーを作成するので、manage.pyのあるprojectディレクトリでコマンドを打ちます。

# project/

$ python3 manage.py makemigrations

$ python3 manage.py migrate

$ python3 manage.py createsuperuser
ユーザー名
アドレス
パスワード
パスワード確認

この時点で管理画面にてTodoリストに何か作成してもいいですし、HTMLファイルまで作成してからリストを作成してもどちらでもいいです。(確認のため)

起動

# project/

$ python3 manage.py runserver
'http://127.0.0.1:8000'を開く

いっきにテンプレートの作成まで進めてしまいます。

projectディレクトリ内のurls.pyを編集

# project/project/urls.py

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todo.urls')),
]

次はtodoディレクトリ内に新しくurls.pyを作成します。

# project/todo/urls.py

from django.urls import path
from . import views


app_name = 'todo'

urlpatterns = [
        path('', views.index, name='index'),
]

そしてテンプレートへ渡すためのviews.pyを編集します。

# project/todo/views.py

from django.shortcuts import render
from .models import Todo


"""一覧表示"""
def index(request):
    todo = Todo.objects.order_by('title')
    return render(request, 'todo/index.html', {'todo': todo})

テンプレートの作成ですが、todoディレクトリ内に新しく「templates」というディレクトリを作成します。

templatesディレクトリ内には「todo」というディレクトリを作り、todoディレクトリの中にHTMLファイルを置きます。

# project/todo

templates
    |
    |----todo
             |
             |----base.html
             |----index.html

ベースのHTMLファイルの作成

<!-- project/templates/todo/base.html -->

<!doctype html>
<html>
    <head>

        <title>TODO</title>

    </head>
    <body>

        {% block content %}
        {% endblock %}

    </body>
</html>

一覧表示用のHTMLファイルの作成

<!-- project/templates/todo/index.html -->

{% extends 'todo/base.html' %}

{% block content %}

    <h2>TODOリスト</h2>

    {% for todos in todo %}
        <ul>
            <li>{{ todos.title }}--{{ todos.created_at }}</li>
        </ul>
    {% endfor %}

{% endblock %}

起動の確認をし、Todoアプリ作成はここまでです。

カスタムコマンド用のスクリプトファイルの作成

ではさっそく「python3 manage.py」に渡すためのスクリプトファイルを作成していこうと思います。

実はDjangoの公式チュートリアルでも説明されています。

公式チュートリアルとは少し異なったやりかたで実装するので、併せて見るとより身に付きやすいかと思われます。(カスタム django-admin コマンドの実装

そして実装内容は「引数指定用のコマンド」→「削除用コマンド」→「作成用コマンド」の順で行います。

先にコマンドが使えるようにするために、todoディレクトリ内に「management」ディレクトリを作り、さらにmanagementディレクトリ内に「commands」ディレクトリを作ります。

イメージ

# project/todo

management
    |
    |----commands
             |
             |----create.py
             |----delete.py

この「create.py」「delete.py」がコマンド「python3 manage.py [create or delete]」に当たる部分です。

それではコマンドに引数を指定できるスクリプトファイルからいきましょう。

引数指定用のスクリプトファイル

まずイメージとしては


$ python3 manage.py コマンド 引数

コマンドに対して渡したい値や文字を指定できるようなスクリプトファイルを作成していきます。

では先ほども説明したように、「todoディレクトリ内」→「managementディレクトリ内」→「commandsディレクトリ内」に「create.py」のファイルを作成してコード記述していきます。

# project/todo/management/commands/create.py

from django.core.management.base import BaseCommand, CommandError
from todo.models import Todo


class Command(BaseCommand):

    """与えられた引数を受け取る"""
    def add_arguments(self, parser):
        parser.add_argument('todo')

    """受け取った引数を登録する"""
    def handle(self, *args, **options):
        todo = options['todo']
        Todo(title=todo).save()
        """公式で推奨されている出力結果"""
        self.stdout.write(self.style.SUCCESS('Successfully create todo'))

ではいきなりですが、実際に動くのか試してみましょう。

既に起動中のローカルサーバーとは別にターミナルを開いて、Djangoプロジェクトの階層まで行き「python3 manage.py create django」と入力し、Djangoアプリをリロードしてみましょう。

# project/

$ python3 manage.py create django
Successfully create todo

引数に与えた「django」がTodoリストに表示されていれば成功です。

削除用のスクリプトファイル

では先ほどcreate.pyファイルでコマンドによるリストの登録を行ったので、次は削除用「delete.py」を作成し、削除していきます。

create.pyと同じ階層にファイルを作ります。

# project/todo/management/commands/delete.py

from django.core.management.base import BaseCommand, CommandError
from todo.models import Todo


class Command(BaseCommand):

    def handle(self, *args, **options):
       """Todoのモデルを変数に渡すだけ"""
        todo = Todo.objects.all()
        todo.delete()
        self.stdout.write(self.style.SUCCESS('Successfully delete todo'))

見て終わかりの通り、引数を受け取るための関数を取り除き、Todoのモデルフィールドを変数に格納しただけです。

取り除いた関数


 """与えられた引数を受け取る"""
def add_arguments(self, parser):
    parser.add_argument('todo')

ではdelete.pyを起動するために、「python3 manage.py delete」を実行してみましょう。

作成用スクリプトファイル

先ほどのcreate.pyにて作成した引数指定用のコードを少し書き換えて、保存したい文字列をハードコードしたファイルへと作り直しましょう。

# project/todo/management/commands/create.py

from django.core.management.base import BaseCommand, CommandError
from todo.models import Todo


class Command(BaseCommand):

    """
    def add_arguments(self, parser):
        parser.add_argument('todo')
    """

    def handle(self, *args, **options):
        """直接渡す"""
        todo = Todo(title='自動で追加。2分後に消える')
        Todo(title=todo).save()
        self.stdout.write(self.style.SUCCESS('Successfully create todo'))

このように改変して実行できることを確認したら、crontabを使って自動化していきたいと思います。

crontabを使って自動化(別で仮想環境も)

ここでは最初に通常の環境でのcrontabでの自動化を試みます。

そして最後に仮想環境での自動化を説明するので、そちらを主に知りたい方は飛ばしてください。

ではcrontabが実行環境内にインストールされているか確認しましょう。

Ubuntuの場合は、rootディレクトリ(最初の階層)にて「/etc/」の階層にcrontabのファイルが置かれているかと思います。

「pwd」コマンドで現在のディレクトリを確認できます。

# project/

$ pwd
/home/ubuntu/project

「cd」コマンドでrootディレクトリに移動した上、「ls」コマンドで指定した階層のディレクトリ内を確認します。

# /home/ubuntu

$ ls /etc/
cron.d
cron.daily
cron.hourly
cron.monthly
cron.weekly
crontab

crontabがあることを確認出来たら、「crontab」を「cron.d」ディレクトリ内にコピーします。

通常はこのように「sudo crontab -e」とすることでエディタモードで開き自動化におけるタイマーを設定するのですが、非推奨とのことなので別のファイルへと書き込みます。

非推奨

$ sudo crontab -e

まずcrontabをcron.dディレクトリ内にコピーします。

ファイル名は拡張子の無い「cron_test」とします。

# /home/ubuntu/

$ sudo cp /etc/crontab /etc/cron.d/cron_test

cron.d内にファイルを置くことによって、設定内容を読み込んでくれます。

vimエディタで中身を確認してみます。

# /home/ubuntu/

$ sudo vi /etc/cron.d/cron_test
# /home/ubuntu/etc/cron.d/cron_test

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17  *    *  *  *   root    cd / && run-parts --report /etc/cron.hourly
25 6    *  *  *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    *  *  7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1  *  *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

基本的な記述方法は、以下となります。

.----------------------------------------分
|  .--------------------------------------時
|  |  .---------------------------------日
|  |  |   .---------------------------月
|  |  |   |   .--------------------曜日
|  |  |   |   |   .-------------ユーザー
|  |  |   |   |   |    .----コマンド
|  |  |   |   |   |    |
m h dom mon dow user command

例:15時25分にpythonファイル実行
25 15 * * * root python3 /home/ubuntu/test.py

お使いのターミナルが日本時間に設定されているか確認しておきます。


$ date
Thu Oct 10 15:48:16 JST 2019

crontabに関してはあまり深く言及しないので各々で調べて強化させてください。

ではさっそくカスタムコマンドをcron_test内の下の方に設定し、自動実行させてみたいと思います。

ふたつのカスタムコマンド用ファイルcreate.pyとdelete.pyファイルを作成したので、それぞれ記述します。

今回は「分」以外指定せずに、毎時間○○分になったら実行という設定です。

# /home/ubuntu/etc/cron.d/cron_test

51  *  *  *  *  root python3 /home/ubuntu/project/manage.py create
53  *  *  *  *  root python3 /home/ubuntu/project/manage.py delete

設定できましたら反映させるためにcronを再起動させます。

# /home/ubuntu/

$ sudo service cron restart
 * Restarting periodic command scheduler cron
 * Stopping periodic command scheduler cron
 * Starting periodic command scheduler cron

51分(設定した時間)に差し掛かったら、起動中のTodoアプリをリロードしてみてください。

このようになっていれば成功です。

そして2分後に設定されていれば消えるはずです。

ここからは仮想環境下での設定例を説明します。

cronで仮想環境を有効にする

私の使用している仮想環境構築ソフトは「venv」ですので、この使用例についてお話しします。

まず仮想環境を有効にするには仮想環境フォルダ内の「bin」ディレクトリにてactivateコマンドをしなければなりません。

# /home/ubuntu/venv/

# 有効
$ source bin/activate

# 無効
(venv)$ deactivate

$

要するに「activate」までのフルパスを先に設定し、「python3 manage.py コマンド」フルパス、そして「deactivate」コマンドはシステム上であれば階層関係なく実行できるので記述するだけ。

「&&」の意味は、「実行されたら次に」といったようなことで、左から順に行います。

# /home/ubuntu/etc/cron.d/cron_test

51  *  *  *  *  root source /home/ubuntu/venv/bin/activate && python3 /home/ubuntu/venv/project/manage.py create && deactivate
53  *  *  *  *  root source /home/ubuntu/venv/bin/activate && python3 /home/ubuntu/venv/project/manage.py delete && deactivate

他にも様々なやり方があるので、詳しくは各々でお調べください。

今回はカスタムコマンドを作成し、crontabを使って登録や削除の自動化を試みましたが、他にも便利な使い方が思いつくはずです。

例えば機械学習での訓練を自動化させたり、モデルフィールドからデータを取得し決まった時間にCSVファイルを作成したりと手間などを解消できそうです。

それでは以上になります。

最後までご覧いただきありがとうございました。