【Django】Webアプリ入門 Part4 モデル作成からモデルインスタンスの表示まで


はじめに

この記事シリーズでは、Python、JavaScript歴1年の大初心者がどうにか頑張ってWebアプリを作ろうとする過程を克明に描き出したいと思います。
なるべく公式ドキュメントやいろいろなサイトを調べて間違いのないようにしますが、これは違うんじゃない?と思ったら是非コメントをください!!
ほかのプログラマ・エンジニアの方の生の声をいただくことは初心者にとっては貴重な学習の機会になります!!

Part4でやること

前回の記事ではView関数を介してWebページを表示させる仕組みや、テンプレートファイルの置き場や呼び出し場所について確認しました。
今回の記事では,モデルを作成する方法や管理ページへの登録の仕方、管理ページとシェルからインスタンスを追加して保存する方法についてまとめたいと思います。

モデルとは

Webサービスを作るうえで、ページを更新したりタブを閉じたりしても消えない情報を扱いたい時があると思います。例えばWeb上で家計簿サービスを作りたいときには、購入した商品の情報は消えないように保存しておかなければなりません。このような挙動はそれらのデータをデータベースサーバに保管し、必要な時に取り出すことによって実現できます。
そして、Djangoプロジェクトの中で外部にあるデータベースサーバとやりとりするために使われるのがモデルです。
データベースを直接操作する代わりにモデルを操作するようにすることで、Django的な発想でデータを管理できます。よってデータベースに関する知識がなくてもモデルの使い方を知っていたら最低限必要な操作はだいたいできます。さらに、データベース管理システム(MySQLやpostgreSQLやSQLite)間では文法が違ったりするのですが、Modelの使用という一元化でその差異も吸収することができます。
そんな便利なモデルとは、具体的には何であるか、どのように作ってどのように使うのかを次から見ていきたいと思います。その際、家計簿アプリに必要なデータを管理することを考えて、それに即したモデルを作りたいと思います。

モデルの作成

構造的な観点から簡潔にいうと、モデルはクラスです。そうすることによって、複数の具体的な情報を、情報の枠組みを表すモデルのインスタンスとして扱おうという発想があります。
それはアプリケーションのmodels.pyに記述され、django.models.Modelというクラスを継承します。モデルはフィールドという概念でデータの要素を表します。
どういうことかというと、例えば人間をデータ化するときは身長、体重、国籍、性別・・・などの細かいデータの寄せ集めによってそれを実現すると思います。この方法でデータ化された人間の情報をDjangoのモデルで扱うときは、人間モデルというクラスを作り、身長のフィールド、体重のフィールド、国籍のフィールド、性別のフィールド・・・というフィールドを人間モデルに持たせます。
つまり、諸データをフィールドとして持つことによって、モデルはまとまりのあるひとつのものごとをデータ化したものとして成立するのです。
このような説明だけではピンとこないと思うので、実際にモデルを作成してみたいと思います。家計簿アプリを作ることが目的だったので支出のデータを扱おうと思います。そして、これからの説明ではこのモデルを使い続けます。
支出をデータ化するときに要素として考えられるのは、何に対してお金を払ったか(支出の名前)、金額、払った日などですね。これに加えてその支出の大まかな分類(ジャンル)があったら便利そうです。ここでは、この4つの情報をフィールドとして持つモデルを作成したいと思います。場所はmy_project/my_app/models.pyというファイルがもともとあると思うので、そこにモデルを定義します。

models.py
class Genre(models.Model):
    genre_name = models.CharField(max_length=100)

class Purchase(models.Model):
    name = models.CharField(max_length=20)
    price = models.IntegerField(default=0)
    purchase_date = models.DateField(auto_now=False)
    genre = models.ForeignKey(Genre,on_delete=models.CASCADE)

コードを追っていきながら説明します。
name,genre,price,purchase_dateというクラス変数が各フィールドを表しています。CharFieldやIntegerField、ForeignKeyなどがフィールドをインスタンスとして作成するためのクラスであり、この種類によってフィールドが保持するデータが文字列なのか、日付なのか、整数値なのか・・・といったことを決定します。Purchaseの定義を一行ずつ見てみます。

name = model.CharField(max_length=20) の文ではnameという名前であらわされるフィールド(支出名を表す)を、文字列を保持するものとして定義しており、さらにmax_length=20の部分で文字列の最大文字数を設定しています。

price = model.IntegerField(default=0) の文ではpriceという名前であらわされるフィールド(金額を表す)を、整数値を保持するものとして定義しており、さらにdefault=0の部分でこのモデルのインスタンスを作成する際にpriceフィールドの値の指定が抜けていたとしたら0として作成すると決めています。

purchase_date = models.DateField(auto_now=False) の文ではpurchase_dateという名前であらわされるフィールド(購入日を表す)を、日付を保持するものとして定義しており、さらにauto_now=Falseの部分でこのフィールドに自動でインスタンス作成時の日時を設定することを回避しています。

genre = models.ForeignKey(Genre,on_delete=models.CASCADE) の文は前の3つと少し毛色が違っていて、この文は、Genreという別のクラスのインスタンス(コード下部)にこのフィールドが定義されているPurchaseクラスのインスタンスが関係付けられることを表しており、このフィールドにはどのGenreインスタンスと関係するか、という情報が入っています。

このように、多彩なフィールドがあり、モデルはその組み合わせによって自由に作成することができます。次にこれらのモデルから早速インスタンスを作成したいところなのですが、モデルは作成しただけではデータベースとの連携が取れません。よって次節ではモデルとデータベースの連携の取り方を扱います。

モデルとデータベースとの連携

前々節に述べた通り、モデルはデータベースサーバとのやりとりのために使われます。そのため、どのようなデータベースサーバとやりとりするかをDjangoプロジェクト内に登録しておかなければなりません。
今回はMySQLというデータベース管理システムによって作られたデータベースを登録したいと思います。MySQLのインストール方法やデータベースの作成方法などについてはたくさんの優れた記事があるためここでは説明を省略します。

データベースの登録はsettings.pyファイルから行います。settings.py中にあるDATABASESというところを見てください。最初は下のようになっているはずです。

settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

この'default'キーに対し、使いたいデータベースの情報を辞書として定義することで、デフォルトで使われるデータベースを識別します。MySQLを使う場合は、以下のように情報を入力します。

settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'my_database',  #データベースにつけた名前
        'USER': 'root',         #データベースのユーザ名
        'PASSWORD': '*******',  #データベースのパスワード
        'HOST': 'localhost',    #データベースサーバのホストコンピュータのIPアドレスやドメイン名など
        'PORT': '3306',         #ポート番号
    }
}

そして次に、ここに登録したデータベースとモデルに対応関係をつけていきたいと思います。MySQLのインストールや設定、settings.py の DATABASES に登録したデータベース名を持つデータベースの作成などは済んでいるものとして話を進めます。まず、下準備として、mysqlclientというpythonがMySQLに接続するための橋渡し的な役目を果たすもの(ドライバ)をインストールします。
pipでは

ターミナル
pip install mysqlclient

conda環境の人は

ターミナル
conda install -c conda-forge mysqlclient

としてインストールします。これはインストールするだけでよく、設定などは要りません。
いよいよデータベースとモデルを対応させます。それは、

python manage.py makemigrations

というコマンドで、モデルの情報から作られたマイグレーションファイルをもとに、

python manage.py migrate

というコマンドでデータベースにアクセスして、設定されたデータベースに、モデルの枠組みに沿ったテーブル(データ構造的なもの)を作成する、という方法によってなされます。実行してみてください。うまくいったでしょうか?models.pyやその他にミスがあったときにはエラーが出ると思います。
ちゃんとモデルによって定義されたデータ群を扱えるようになったのかを確認するために、次節でインスタンスを作ってみて表示させてみたいと思います。

モデルインスタンスの作成、保存、表示

モデルはクラスであるため、モデルで定義されたフィールドを持つ個々のデータはインスタンスとして表すことができます。作り方もふつうのクラスと同じようにモデルをコンストラクタとして使用すればいいだけです。早速やってみましょう。まずは、シェルからインスタンスを作成してみたいと思います。次をターミナルに入力してみてください。

ターミナル
python manage.py shell

これで対話モードに入ると思います。カレントディレクトリはプロジェクトディレクトリです。まずは作成したモデルをインポートします。

ターミナル
>>>from my_app.models import Purchase,Genre

PurchaseのインスタンスはGenreのインスタンスと関係を付けなければならないので、まずはGenreのインスタンスから作ります。

ターミナル
genre = Genre(genre_name="食費")

フィールド=値とすることで、そのフィールドの値が決定したGenreインスタンスを作ります。しかし、ただ作っただけではデータベースに反映されません。反映するには以下のようにします。

ターミナル
genre.save()

こうすることで作成したモデルインスタンスをデータベースに保存できます。次にPurchaseインスタンスを作成して保存します。DateFieldに保存されるものはdatetime.dateというpython組み込みモジュールのインスタンスなので、まずそれをimportします。

ターミナル
from datetime import date
purchase = Purchase(name="昼ごはん",price=500,purchase_date=date.today(),genre=genre)

genreフィールドは先ほど作ったインスタンスそのもので初期化できます。これを先ほどと同様に保存します。

ターミナル
purchase.save()

これらのインスタンスが反映されているかシェルで確認します。次のコードを見てください。

ターミナル
genre_names = [genre.genre_name for genre in Genre.objects.all()]
print(genre_names) #['食費']と表示される
purchase_names = [purchase.name for purchase in Purchase.objects.all()]
print(purchase_names) #['昼ごはん']と表示される

データベースに登録されているインスタンスは、モデル.objects.all()でそのすべてを取り出すことができます。これでちゃんと追加されたことが分かりました。
しかし、いちいちターミナルの黒い画面を眺めながらデータの操作や確認を行うのは面倒です。Djangoにはモデルに関わるデータをWeb上でグラフィカルに管理できるadmin機能が備わっています。それを使うと一目でデータの確認ができたり、直感的なインスタンス作成ができたりします。それを使ってみましょう。

モデルやそのインスタンスの情報を一括的に管理できるサイトにアクセスできるのはサイト管理者(とその関係者)のみであるべきです。一般ユーザがほかの人のデータ全てを見ることができるということがあってはならないからです。よって、Djangoには、管理者、つまり管理サイトに対して一番強い権限を持つユーザを作成するコマンドがあります。それは以下の通りです。

ターミナル
python manage.py createsuperuser

これを実行し、ユーザ名、メールアドレス、パスワードを入力すると、管理者ユーザが作られます。Djangoは管理者ユーザであるかどうかを識別することでサイトへアクセスできるかどうかを決めています。(ほんとうは管理者が他のユーザのアクセス権も決めることができるのですが、この記事シリーズでは扱いません。)
このユーザで管理サイトにアクセスしてみましょう。my_project/my_project/urls.py (my_projectはプロジェクト名)で、既にpath('admin/', admin.site.urls)と登録されているので、python manage.py runserverでサーバを立ち上げてlocalhost:8000/admin とします。

上のようなページが開かれるはずです。これに先ほど登録したユーザ名とパスワードを入力してログインを押します。すると、

このような画面になるはずです。これを見て分かる通り、PurchaseやGenreモデルが表示されていません。管理したいモデルは登録する必要があって、それはmy_appにあるadmin.pyへの記述によってなされます。次のようにして記述します。

admin.py
admin.site.register(Purchase)
admin.site.register(Genre)

こののち、再び管理サイトを開いて更新します。すると、

次のようにPurchase,Genreが表示されます。このサイトでPurchaseインスタンスを追加してみます。Purchaseの追加をクリックしてください。

上のように表示されるはずです。ターミナルから登録するより直感的にできるようになりました。

まとめ

今回はモデルの概要、作り方、データベースとの連携のさせ方。インスタンスのシェルからの作成、管理サイトからの作成について学びました。次の記事では、ユーザがサービス上でデータを登録、確認、更新、削除できるようにしていきたいと思います。なにか疑問に残る点がありましたらコメントをください。