このページでは、Django におけるモデル(Model)について解説していきます。
Django で開発するウェブアプリの基本構造は MTV モデルとなっています。そして、この M の部分がモデルとなります。このように、Django で開発するウェブアプリの基本構造の1つであり、モデルはウェブアプリにおいて重要な存在となります。
ただ、MTV という言葉やモデルの存在を知っている方でも、モデルがどのような役割を果たすものであるかについてはピンときていない方も多いのではないかと思います。
そんな方に向け、このページでは、Django におけるモデルの基本について、特に Django 初心者の方向けに詳しく・分かりやすく解説していきたいと思います。
モデルとは?
まず、Django におけるモデルとは、ウェブアプリを構成する Python スクリプトからデータベースを操作するための仕組みとなります。もっと簡単に言えば、データベースの操作を実現するクラスとなります。
さらに、モデルはデータベースで扱う(データベースに保存する)データの種類を定義する役割も持ちます。
モデルとは「データベースの操作を実現するクラス」
ウェブアプリには、データベースを操作してデータの保存や取得を行うものが多いです。
ここからは、Twitter などの例を用いてモデルの重要性について解説していきますが、Twitter の作りや動作はあくまでも私の予想・想像のものとなるので注意してください
あくまでもモデルの重要性を理解していただくことを目的に、実際のウェブアプリの例として Twitter を使用させていただいています
ウェブアプリにおけるデータベース操作の必要性
例えば Twitter であれば、「ツイート」や「いいね!」や他のユーザーの「フォロー」等を行うことができます。これらを行なった際、これらの「ツイート」された情報のデータや「いいね!」した情報のデータ、ユーザーを「フォロー」した情報のデータが Twitter アプリに送信され、それらのデータが Twitter アプリからデータベースに保存されるようになっているのではないかと思います。
さらに、これらのデータベースに保存されているデータは後から取得して表示することが可能です。例えば Twitter では自身や他のユーザーの「ツイート」の履歴や「いいね!」した履歴を表示することが可能となっています。また、自身が「フォロー」しているユーザーの情報も表示することができます。これが可能なのは、前述の通り、データベースにデータが保存されているからで、この保存されているデータを取得することで、後から様々な情報を表示できるようになっています。
ここでは Twitter を例に挙げましたが、保存されるデータの種類は異なるものの、インスタグラムや Facebook など、他のウェブアプリにおいても大体こんな感じでデータベースにデータが保存されていることが想像できるのではないかと思います。
このように、ウェブアプリではデータベースにデータを保存したり、データベースからデータを取得することで様々な機能を実現しているものが多いです。
モデルにおけるデータベース操作
そして、これらのデータの保存やデータの取得など、データベースに対する操作を行うのがモデルの役割の1つとなります。
このモデルでのデータベースに対する操作の特徴は、メソッドの実行によって操作が実現できる点になります。
通常であれば、データベースに対する操作は DBMS と呼ばれるデータベース管理システムに対し、クエリ(依頼)を送信することで実現されることになります。そのため、データベースの操作を行いたい場合、その操作に対応したクエリを作成する必要があり、この際にクエリや SQL 等の知識が必要となります。
ですが、Django では、モデルを利用することでデータベースに対する操作をメソッドの実行により実現することができます。これらのメソッドが、メソッドや引数等に応じたクエリを生成し、それを DBMS に対して発行してくれます。したがって、ウェブアプリの開発者はクエリの知識がなくても Python でのメソッドの実行の仕方を知っていれば、データベースの操作を実現することができることになります。
補足しておくと、前述の通りウェブアプリを単に開発するだけであればモデルやメソッドの利用ができれば十分です。ですが、特にウェブアプリの処理速度・パフォーマンスの向上を図る際にはデータベースやクエリの知識が役立ちますので、これらの知識があるに越したことはありません。
特に、Django ではクエリの知識なしでウェブアプリが開発できてしまうので N + 1
問題という問題が発生しやすいです。この辺りの解説は Django 入門13の下記のページで解説していますので、まずはクエリに関しては意識せずにウェブアプリ開発に取り組んでいただくので良いと思います。
スポンサーリンク
ウェブアプリを特徴づけるのは「モデル」
このデータベースに関して特に重要なポイントは、データベースに保存するデータの種類によってウェブアプリの特徴が決まるという点になります。
ウェブアプリで扱うことのできるデータは、基本的には、その時にユーザー(クライアント)から受信したデータとデータベースに既に保存されているデータのみとなります。
特に、後からデータを利用したい場合は、基本的にはそのデータをデータベースに保存しておく必要があります。これは別に Django やウェブアプリに限った話ではなく、他のプログラミングにおいても同様ですよね。後から利用したいデータは、変数やファイル等に保存しておく必要があります。特にウェブアプリの場合は、その保存先がデータベースとなります。
さらに、ウェブアプリではデータの利用やデータの加工によって実現される機能が多いです。そして、扱うデータの種類によって、ウェブアプリで実現可能な機能も変化することになります。そのため、「データベースに保存するデータの種類」はウェブアプリの機能の実現の面で非常に重要な要素となります。
例えば Twitter であれば、ユーザーが「いいね!」した情報のデータが保存されることで、自身のツイートに「いいね!」した人数を確認することができるようになっています。自身のツイートに「いいね!」してくれた人数を確認することが好きな方も多いと思いますし、この機能は Twitter の特徴の1つであると言っても良いと思います。
この機能が実現できるのは、各ツイートに対する “「いいね!」をした人数を判断可能とするデータ” がデータベースに保存されているからです。例えば、ツイートに対して「いいね!」をした人数そのものや、ツイートに対して「いいね!」をしたユーザーの情報を保存しておくようにしておけば、後から「いいね!」された回数を表示することができます。
ですが、こういったデータが保存されていなかった場合、後から自身のツイートに「いいね!」してくれた人数を表示することができず、上記のような機能はウェブアプリで実現できないことになります。つまり、データベースに保存するデータの種類を減らせば、ウェブアプリの特徴も減ることになるのです。
このように、データベースに保存するデータの種類はウェブアプリの特に機能的な特徴を決定する重要な要素となっています。
そして、このデータベースに保存するデータの種類を決定するのもモデルの役割となります。つまり、ウェブアプリの機能的な特徴を決定するのはモデルと言っても過言ではありません。
そのため、もしかしたらモデルに地味な印象を抱いている方もおられるかもしれませんが、実はウェブアプリの特徴の骨格を形成する非常に重要な役割を担うのがモデルとなります。
ウェブアプリで扱いたいデータがあったとしても、そのデータ、もしくはそのデータの元となるデータが保存されていなければ、そのウェブアプリではそのデータを扱うことができません。例えば、健康管理ウェブアプリで日々の BMI を表示できるようにウェブアプリを開発しようとしているのに、データベースに保存するデータが身長のみであれば、日々の BMI を表示するような健康管理ウェブアプリを開発することはできないのです(BMI の算出には身長だけでなく体重が必要)。
あなたが開発してみたいウェブアプリはどのようなものでしょうか?そのウェブアプリはデータの保存を行う必要がありますか?
データの保存を行う必要がある場合、そのウェブアプリの機能を実現するためにデータベースへの保存が必要なデータの種類を考え、それらを保存できるように適切にモデルを実装していく必要があります。
モデルを実装するファイル
そして、このモデルの実装先となるファイルは models.py
になります。この models.py
にモデルを作成(定義)していくことになります。
Django でウェブアプリを開発する際には、下記のように startproject
コマンドと startapp
コマンドを実行することになりますが、models.py
は後者の startapp
コマンドにより自動的に生成されるファイルとなります。
% django-admin startproject プロジェクト
% python manage.py startapp アプリ
そして、この models.py
は startapp
で生成されるアプリ毎に用意されることになり、必要に応じて各アプリ毎に実装を行なってモデルを作成していく必要があります。
また、前述の通りモデルの定義先のファイルは models.py
となりますが、モデルは他のファイルから利用されるものであり、モデルを利用するのは基本的にはビュー(views.py
)となります。
データベースとモデルの関係
次は、モデルとデータベースの関係性について解説していきたいと思います。
また知識的な話になるのでウンザリされている方もおられるかもしれませんが、ここを理解しておくと Django でのデータベース操作時のエラー時の解決にも役立ちますし、モデルの役割をより具体的に理解していくことができると思います。
スポンサーリンク
データベース関連の用語の整理
まず、モデルの話に移る前にデータベース関連の用語の整理をしておきたいと思います。
テーブル
まず、データベースには複数のテーブルが用意され、各テーブルによって複数のデータが関連付けられて管理されていくことになります。
テーブルは Excel 等で作成する「表」のようなものです。例えば下図はテーブルの一例となります。一応、クラスの生徒のデータを管理することを想定したテーブルとなっています。
フィールド(カラム)
テーブルは複数の列から構成されており、この列のことをフィールドやカラムと呼びます。
さらに、この列名のことをフィールド名やカラム名と呼びます。本当はテーブルの説明を行う際にはフィールドよりもカラムという言葉が適切な気もしていますが、Django においてはフィールドという名前を使った方が実装と話が合って分かりやすいと思いますので、フィールドという名称を利用させていただきます。
テーブルで管理するデータは、テーブルの持つフィールドによって決定されることになります。例えば上の図のテーブルであれば、下記の4つを管理するテーブルということになりますね!
- ID
- 名前
- 算数の点数
- 国語の点数
もし、フィールドに「所属クラブ」を追加すれば、下記のように5つのデータを管理することができるようになります。
- ID
- 名前
- 算数の点数
- 国語の点数
- 所属クラブ
このように、テーブルで扱うデータはフィールドによって決まるという点は重要なので、まずはこの点を覚えておいてください。テーブルで管理したいデータに応じてフィールドを適切に設ける必要があります。
また、各フィールドでは異なる型のデータが扱われるという点も重要となります。例えば先ほど示した図のテーブルにおいては「名前」のフィールドは文字列を扱うのに対し、その他のフィールドは整数を扱うようになっています。
各フィールドにおいて扱いたいデータの型は異なるはずなので、テーブルで扱いたいデータに応じたフィールドを設けるとともに、各フィールドで扱うデータの型を決める必要もあります。
レコード
ここまではテーブル、およびテーブルの列(フィールド)に対して説明をしてきましたが、次は「行」に対して説明を行なっていきます。
この、テーブルにおける行のことは「レコード」と呼ばれます。テーブル内の各レコードは、テーブルと同じフィールドを持ちます。
例えば、上の図のテーブルにおける各レコードには、テーブルと同様に下記の4つのフィールドを持つことになります。
- ID
- 名前
- 算数の点数
- 国語の点数
また、レコードの各フィールドには具体的なデータが設定されていることになります。例えば上の図において ID が 2
のレコードでは、下記のようにデータが設定されていることになります。このレコードから、ID が 2
の生徒の成績を確認できるということになります。
- 名前:山田太郎
- 算数の点数:
95
- 国語の点数:
84
さらに、データベースのテーブルにおいては、データはレコード単位で管理するようになっています。例えば、テーブルに対してレコードを新規作成や、レコードの削除などの操作を行うことができます。
これらのテーブルに対する代表的な操作は、作成(Create)・読み取り(Read)・更新(Update)・削除(Delete)の頭文字をとって CRUD と呼ばれたります。この CRUD という言葉を聞くことも多いと思いますので、この機会に覚えておくと良いと思います。
また、テーブル内の各レコードを確実に見分けることができるように、フィールドにはプライマリーキー(主キー)となるフィールドが必要となります。プライマリーキーとは、テーブル内のレコードを一意に識別するためのフィールドで、このフィールドはテーブル内のレコード間で重複があってはいけません。例えば、ここまで紹介してきた生徒を管理するテーブルであれば、ID のフィールドをプライマリーキーとすることを想定しています。
プライマリーキーには ID ではなく他のフイールドを設定することも可能です。ただし、前述の通りプライマリーキーに設定したフィールドは重複は許されないため、重複する可能性のあるフィールドはプライマリーキーにしない方が良いです。例えばクラスの生徒の名前は重複する可能性もあるため、プライマリーキーには設定しないほうが良いでしょう。
モデルとデータベースの関係
さて、ここまでデータベースについての説明を行なってきました。
次は、モデルとデータベースの関係について解説していきます。
クラス:テーブル
モデルとは? の冒頭で解説したように、モデルとは要は「クラス」です。django.db.models.Model
というクラスのサブクラスがモデルとなります(以降、django.db.models.Model
を単に Model
と呼ばせていただきます)。
例えば下記における Student
は、Django では単なるクラスではなく、モデルとして扱われます。
from django.db import models
class Student(models.Model):
略
以降、この Model
というクラスのサブクラスを「モデルクラス」と呼びます(単にモデルと言っても差し支えないのですが、Model
と区別するため&クラスであることを意識してもらうために、このページでは「モデルクラス」と呼ばせていただきます)。
そして、このモデルクラスがデータベースにおけるテーブルとなります。
厳密に言えば、モデルクラス = テーブル + α
です。モデルクラスはテーブルの定義以外にもメソッドやメタデータ等を持っています。
このモデルクラスは、前述の models.py
に定義することになります。
また、前述の通り、テーブルにはフィールド(カラム)が存在します。そのため、モデルクラスを作成する際には、このクラスにフィールドを定義する必要があります。
このフィールドは、クラス変数として定義を行います。
ただし、単にクラス変数を定義すれば良いというわけではなく、クラス変数の定義の右辺に Django フレームワークに用意された Field
のサブクラスのインスタンスを指定した場合のみ、そのクラス変数がフィールドとして扱われるようになります。そして、この Filed
のサブクラスは複数存在し、このサブクラスによって各フィールドで扱うデータの型が決まります。
さらに、クラス変数における変数名がテーブルにおけるフィールド名となります。
Filed
のサブクラスは多くの種類が用意されており、各 Filed
のサブクラスに関しての説明ページもいずれ公開したいと思っています。ひとまず、このページでは下記の Filed
のサブクラスを利用して説明を行なっていきます。
CharField
:文字列を扱うFiled
のサブクラスIntegerField
:整数を扱うFiled
のサブクラス
例えば、下記のようにクラス変数を定義した場合、1つ目のクラス変数の定義によりテーブルに name
というフィールドが加えられ、さらにこの name
フィールド、すなわち name
の列においては、文字列のデータを扱うことになります。また、2つ目のクラス変数の定義により、テーブルに math
というフィールドが加えられ、さらにこの math
フィールドでは整数のデータを扱うことになります。他のクラス変数でも同様ですね!
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=100) #最大100文字
math = models.IntegerField()
japanese = models.IntegerField()
つまり、上記のモデルクラスは次の図のテーブルとして考えることができることになります。いきなり id
というフィールドが追加されていますが、このフィールドはプライマリーキーであり、プライマリーキーに設定するフィールドの指定を行わなかった場合に自動的に追加されるフィールドとなります。
このように、モデルクラスそのものがテーブルとなり、クラスのクラス変数がフィールドの定義を行うことになります。したがって、Django でのウェブアプリ開発者は、テーブルで扱いたいデータの種類に応じて適切にモデルクラスの定義を行う必要があります。
また、各テーブルには名前が設定されることになり、この名前は アプリ名_クラス名
となります(ただし、テーブル名においてはクラス名が全て小文字となります)。つまり、上記のモデルクラスの場合、アプリ名を例えば ModelApp
とすれば、テーブル名は ModelApp_student
となります。
ここで一点補足しておくと、モデルクラスのフィールドの定義に使用する Field
のサブクラスは、前回の連載の下記ページでの解説で紹介したフォームクラスのフィールドの定義に使用する Field
のサブクラスとは異なるので注意してください
これらは異なるモジュールで定義されており、そのため、Field
のサブクラスを利用するために import
するモジュールも異なることになります
インスタンス:レコード
モデルクラスがテーブルであるのに対し、モデルクラスのインスタンスは、そのモデルクラスのテーブルに対応するレコードとなります。厳密に言えば、モデルクラスのインスタンス = レコード + α
です。モデルクラスのインスタンスは各フィールドのデータの値以外にもメソッドやメタデータ等を持っています。また、インスタンスというよりもオブジェクトと言う方がしっくりくる方もおられるかもしれませんが、クラスと対比するためにインスタンスという言葉を使って説明していきます。
モデルクラスのインスタンスでは、クラスと同じフィールドをデータ属性として持つことになります。そして、このデータ属性からレコードの各フィールドに対してアクセスすることが可能です。このフィールドには具体的なデータを設定することができますが、各フィールドに設定可能なデータの型はクラス変数での定義によって異なることになります。
例えば、前述の Student
クラスの場合、このクラスのインスタンスからはデータ属性として name
・math
・japanese
(+ id
)が利用可能で、これらの値を読み込んだり、値を設定したりすることが可能となります。ただし、name
には文字列、math
と japanese
には整数のみが設定可能となります。なぜなら、そのようにモデルクラスのクラス変数に Filed
のサブクラスを設定しているからです。
さらに、モデルクラスには、レコードへの操作を行うメソッドが用意されています。そして、これらのメソッドはインスタンスから実行可能であり、このメソッドの利用によってテーブルに対してレコードを新規作成したり、レコードを削除したりすることができます。
例えば、前述の Student
クラスのインスタンスを利用して下記のような処理を行なった場合の動作について考えてみましょう。
student = Student()
student.name = '山田太郎'
student.math = 95
student.japanese = 84
student.save()
まず1行目で Student
クラスのコンストラクタが実行されてインスタンスが生成され、そのインスタンスを変数 student
が参照することになります。このインスタンスは、前述の通り Student
のテーブルのレコードとして扱われ、student
からは name
・math
・japanese
のフィールドにデータの設定を行うことが可能です。ただし、まだこのレコードは新規作成したばかりなので、各フィールドには初期値が設定されていることになります。
次に、2行目から4行目で各フィールドに対してデータが設定されることになります。このように、インスタンス毎に各フィールドにデータを設定することが可能であり、これはレコードの各フィールドにデータを記録するのと同じ操作になります。
さらに、5行目では student
が save
というメソッドを実行しており、これによりレコードが Student
のテーブルに保存されることになります。この例の場合、コンストラクタによって新規作成されたインスタンスが save
メソッドを実行しているため、テーブルに対してレコードの新規作成が行われることになります(レコードの新規作成時には、プライマリーキーである id
が自動的に設定されることになります)。
この例からも分かるように、モデルクラスのインスタンスはテーブルにおけるレコードとして扱うことができます。
また、上記のようなインスタンスの生成やインスタンスからのメソッドの実行は、基本的にはビュー(views.py
)から行うことになります。
モデルクラスの定義
次はモデルクラスの定義方法について解説していきます。
スポンサーリンク
Model
のサブクラスとしてクラスを定義
クラス:テーブル でも簡単に示しましたが、Model
のサブクラスとして定義したクラスがモデルクラスとなります。Model
は django.db.models
で定義されていますので、このモジュールを import
して Model
を利用する必要があります。
モデルクラスの定義先のファイルは models.py
となります。
from django.db import models
class モデルクラス名(models.Model):
〜略〜
フィールドの定義
また、これも クラス:テーブル で説明したように、そのモデルクラスはデータベースにおけるテーブルの役割を果たします。テーブルにはフィールドが存在するはずなので、モデルクラスに関してもフィールドを持たせることが必要になります。
そして、モデルクラスにおける Field
のサブクラスのインスタンスとして定義したクラス変数がフィールドとなります。クラス変数の名前がフィールド名となり、Field
のサブクラスの種類でそのフィールドに格納可能なデータの型が決まります。
from django.db import models
class モデルクラス名(models.Model):
クラス変数名1 = Fieldのサブクラスのコンストラクタ()
クラス変数名2 = Fieldのサブクラスのコンストラクタ()
フィールドへの引数指定
また、Field
のサブクラスのコンストラクタへの引数の指定により、データの型だけでなく、様々な制約を各種フィールドに追加することができます。また、各種フィールドの詳細な設定を行うことも可能です。要は、フィールドのカスタマイズをすることができます。
from django.db import models
class モデルクラス名(models.Model):
クラス変数名1 = Fieldのサブクラスのコンストラクタ(引数1, 引数2, 引数3)
クラス変数名2 = Fieldのサブクラスのコンストラクタ(引数1, 引数2)
フィールドへの引数の指定例
例えば、CharField
には max_length
引数を指定可能です。というよりも、この引数に関しては指定が必須となっています。CharField
のフィールドに保存可能なデータの型は文字列ですが、max_length
を指定することで保存可能な文字列の最大長という制約をフィールドに加えることができます。
from django.db import models
class TestModel(models.Model):
# nameフィールドを32文字以下に制限する
name = models.CharField(max_length=32)
また、多くの Field
のサブクラスのコンストラクタでは unique
引数を指定することが可能で、unique=True
を指定することで同じテーブルのレコード間における、そのフィールドのデータの重複不可という制約を追加することもできます。
from django.db import models
class TestModel(models.Model):
# emailフィールドの重複を禁止する
email = models.EmailField(unique=True)
さらに null=True
を指定することで、そのフィールドへのデータの保存が必須となります。この場合、そのフィールドが空の状態ではデータベースに保存することできません。この引数に関してはデフォルト設定が null=True
となっているため、そのフィールドが空の状態でもデータベースに保存できる様にするためには null=False
を指定する必要があります。
from django.db import models
class TestModel(models.Model):
# ageフィールドの空白を許可する
age = models.IntegerField(null=True)
他にも、primary_key=True
を指定することで、そのフィールドをプライマリーキーに設定するようなことも可能です。プライマリーキーのフィールドはレコード間でデータが重複することは許されませんので、自然と unique=True
を指定した場合と同じ制約が追加されることになります。
from django.db import models
class TestModel(models.Model):
# emailフィールドをプライマリーキーとする
email = models.EmailField(primary_key=True)
これらの制約を満たさない場合、テーブルにレコードを保存する様な際に例外が発生することになります。つまり、制約を満たさないレコードがテーブルに保存されることを禁止し、ウェブアプリとして保存することが妥当であるデータのみが保存されるようにすることができます。
指定可能な引数の調べ方
ここまで紹介した引数は一例であり、フィールドには他にも多くの引数を指定可能です。指定可能な引数は、例えば下記の Django 公式のページが参考になると思います。
https://docs.djangoproject.com/ja/4.1/ref/models/fields/
また、開発環境によっては指定可能な引数を一覧表示してくれるようなものもあります。例えば私は VSCode を利用していますが、Python プラグインを入れておけば、下の図のように Field
のサブクラスのコンストラクタの記述を行う際に指定可能な引数一覧を示してくれるようになっています。
こういった情報を参照しながら、フィールドへの引数指定を行ってフィールドのカスタマイズを試してみると良いと思います。
スポンサーリンク
メソッドの追加
また、通常のクラス同様に、モデルクラスに対してもメソッドを追加することが可能です。そのモデルクラス独自のメソッドを追加しても良いですし、モデルクラスのスーパークラスである Model
の持つメソッドをオーバーライドするようなことも可能です。
from django.db import models
class モデルクラス名(models.Model):
クラス変数名1 = Fieldのサブクラスのコンストラクタ()
クラス変数名2 = Fieldのサブクラスのコンストラクタ()
def メソッド名(self, 他の引数):
メソッドの処理
モデルクラス独自のメソッドの定義
例えば、下記の様にモデルクラスを定義すれば、2つの数値の平均を求め、それを average
フィールドに格納するようなメソッドがインスタンスから利用できるようになります。
from django.db import models
class Student(models.Model):
math = models.IntegerField()
japanese = models.IntegerField()
average = models.FloatField()
def set_average(self):
self.average = (self.math + self.japanese) / 2
メソッドのオーバーライド
また、下記のようにモデルクラスを定義すれば、Model
の持つ save
メソッドがオーバーライドされ、インスタンスから save
メソッドを実行した際に、このモデルクラスの save
メソッドが実行されるようになります。さらに、super().save()
を実行することで、スーパークラスの save
メソッド、すなわち Model
の持つ save
メソッドが実行され、通常のデータベースへの保存処理も行うことができます。
from django.db import models
class Student(models.Model):
math = models.IntegerField()
japanese = models.IntegerField()
average = models.FloatField()
def save(self, *args, **kwargs):
self.average = (self.math + self.japanese) / 2
super().save(*args, **kwargs)
保存する前に何かしらの処理、保存した後に何かしらの処理を行いたいような場合に、この save
メソッドのオーバーライドが役立ちます。
__str__
メソッドの定義
あと覚えておくと良いのが __str__
メソッドになります。この __str__
メソッドを定義することで、モデルクラスのインスタンスそのものが出力された際の出力文字列を定義することができます。
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
__str__
メソッドについては詳細を下記ページで解説していますので、詳しくは下記ページをご参照ください。
モデルを使いこなすための知識
ここまでの説明で、モデルがどういったものであるのかについてはイメージが湧くようになったのではないかと思います。
ここからは、もう一歩踏み込んで、モデルを使いこなすために必要な知識について説明をしていきます。
マネージャーを利用してモデルクラスを操作する
インスタンス:レコード の節で、レコードをテーブルに新規作成する例を示しました。このレコードの新規作成は、インスタンスにメソッドを実行させることで実現することができましたね!
このレコードの新規作成は、レコードの削除や更新・表示に比べると手順が簡単です。なぜなら、テーブルからのレコードの取得が不要だからです。新規作成の場合、コンストラクタでインスタンスを生成し、それに save
メソッドを実行させれば良いだけであるため、事前にテーブルからレコードを取得しておく必要はありません。
それに対し、レコードの表示を行うことを考えた場合、事前にテーブルから所望のレコードを取得しておく必要があります。
レコードを取得する前には当然そのレコードを使用することができないため、新規作成時のようにレコードに対応するインスタンスにメソッドを実行させてレコードを取得するようなことはできません。
マネージャー
このような場合に便利なのがマネージャーとなります。このマネージャーは、モデルクラスに対して、つまりテーブルに対して操作を行うためのオブジェクトです。各モデルクラスはこのマネージャーを持っており、クラス名.objects
によりモデルクラスからマネージャーを利用することができます。
モデルクラスのインスタンスからはメソッドの実行によってレコードを操作することができるのに対し、
モデルクラスの objects
からはメソッドの実行によってテーブルを操作することができます。
また、objects
はインスタンスではなくモデルクラスから利用するものとなります。インスタンスが無くても利用することができるため、この objects
にメソッドを実行させることで、インスタンスを用意することなく、テーブルへの操作を行うことが可能となります。
そして、このマネージャーを利用することで、テーブルからレコードを取得するようなことが可能となります。例えばテーブルから全レコードを取得したり、条件を設定し、その条件に合致したレコードを取得するようなことができます。なので、ウェブアプリでデータベースのデータを表示するような機能を実現する際にはモデルクラスのマネージャーを利用することになります。それに対し、ウェブアプリでデータを保存するような機能を実現する際にはモデルクラスのインスタンスを利用することが多いです。
マネージャーによるレコードの取得
特にマネージャーはレコードの取得時に利用することが多いので、次はマネージャーによるレコードの取得について解説していきます。
マネージャーによってテーブルから取得されるレコードは、そのモデルのインスタンス、もしくはモデルのインスタンスの集合(QuerySet
)となります。例えば、下の図のような Student
クラスのテーブルについて考えてみましょう!
このテーブルから math
が 95
である生徒のレコードを取得したいとします。前述の通り、レコードの取得はマネージャーを利用することで実現でき、具体的には、objects
から filter
メソッドを実行させることで実現することができます。
students = Student.objects.filter(math=95)
filter
メソッドは、モデルクラスに対応するテーブルから引数で指定した条件に該当するレコード(インスタンス)の集合を取得するメソッドになります。下記では Student.objects
から filter
メソッドを実行しているため、Student
のテーブルから math
フィールドが 95
であるレコードをまとめた QuerySet
が取得されることになります。
この QuerySet
はリスト等のようにイテラブルな型となります。なので、リストなどのように for
ループを利用して QuerySet
内の各要素を取得し、その各要素1つ1つに対して処理を行うようなことも可能です。そして、QuerySet
の各要素はレコード、つまりはレコードの取得元のモデルクラスのインスタンスとなります。
したがって、上記の filter
メソッドを実行して取得した QuerySet
を利用すれば、次のように math=95
を満たすレコード(インスタンス)の情報を表示することが可能となります。
students = Student.objects.filter(math=95)
for student in students:
print(student.id)
print(student.name)
print(student.math)
print(student.japanese)
ここでは各レコードの情報を print
関数を利用して表示を行なっていますが、実際のウェブアプリでは、テンプレートファイルにこれらの情報を埋め込んで HTML として表示を行うことになります。
また、前述で示した Student
のテーブルには math
フィールドが 95
のレコードが2つ存在するため、QuerySet
は2つのレコードの集合となります。そのため、上記の for
ループでは2つ分のレコードの情報が表示されることになります。
テーブルからのレコードの取得は条件に該当するレコードを検索することによって実現され、その条件に該当するレコードが複数存在する可能性があるため、objects
のメソッドの返却値は、レコードそのものではなく、レコードの QuerySet
となるケースが多いです。上記の filter
メソッドも返却値が QuerySet
となるケースの1つとなります。検索結果が1つであっても QuerySet
で返却されますし、検索結果が1つもない場合であっても QuerySet
が返却されます(この場合は空の QuerySet
が返却されます)。
また、objects
のメソッドにはインスタンスそのものを返却するものもあります。例えば get
メソッドはその例の1つで、この場合はメソッドの返却値をそのままインスタンスとして利用できます。
student = Student.objects.get(id=2)
print(student.id)
print(student.name)
print(student.math)
print(student.japanese)
get
メソッドの返却値は QuerySet
ではなく、レコード(インスタンス)そのものであるため、filter
メソッド等とは返却値の扱い方が異なります。
ここまでの説明のように、テーブルに対する操作は、そのテーブルのモデルクラスのマネージャー objects
を利用して実現することができます。上記ではテーブルに対してレコードの取得の操作を行う例を示しましたが、実は インスタンス:レコード で示したレコードの作成もテーブルに対する操作の1つなので、objects
にメソッドを実行させることで実現することもできます。
下記は、objects
を利用してレコードを作成する例となります。下記の処理を実行した場合、処理を実行した時点でテーブルにレコードが新規作成されることになります。それに対し、コンストラクタでインスタンスを生成した場合は、save
メソッド等のレコード保存のための処理を行わない限りテーブルにレコードは新規作成されません。
Student.objects.create(name='山田太郎', math=95, japanese=81)
このように、テーブルへの操作はレコードに対応するインスタンスを生成・取得し、そのレコードにメソッドを実行させることで実現することもできますし、objects
のメソッドを利用して直接テーブルを操作することも可能です。
ただし、テーブルからのレコードの取得に関しては、まだレコードを取得していない状態(インスタンスが存在しない状態)となるため、objects
のメソッドを利用して取得する必要があります。
また、objects
のメソッドから取得した結果(返却値)は、レコードそのもの(モデルクラスのインスタンス)である場合もありますが、QuerySet
である場合もあります。この辺りは使用するメソッドによります。さらに、この QuerySet
は集合であり、各要素はモデルクラスのインスタンスとなります。このインスタンスを利用して、レコードの情報の表示や更新などを行うことが可能となります。
特にマネージャーや QuerySet
に関してはまだまだ説明不足の点がありますので、いずれ別ページを作成して詳細な解説をしたいと思います。
スポンサーリンク
マイグレーションでモデルクラスを DB に反映する
続いて、モデルを利用する際に重要となるマイグレーションについて説明していきます。
モデルクラスは定義しただけではデータベースに反映されない
ここまで説明してきた通り、モデルクラスはデータベースのテーブルとして扱われ、そのモデルクラスの1つのインスタンスが、そのテーブルの1つのレコードとして扱われることになります。
ただし、models.py
にモデルクラスを定義しただけではデータベースにテーブルは作成されないという点に注意してください。
そして、テーブルが作成されていない状態でウェブアプリが動作すると、テーブルが存在しないため、テーブルからレコードの取得を行なったり、テーブルにレコードを保存しようとする際にエラー(例外)が発生することになります。
つまり、モデルクラスは定義するだけではダメで、モデルクラスを定義した後に、そのモデルクラスの内容をデータベースに反映してテーブルを作成するような操作を別途行う必要があります。
マイグレーションでデータベースに反映する
そして、この操作がマイグレーションとなります。このマイグレーションは、プロジェクト作成時に生成される manage.py
を利用して下記の2つのコマンドを実行することで行うことができます。
% python manage.py makemigrations % python manage.py migrate
1つ目のコマンドでは、定義されているモデルクラスに基づいてマイグレーション実行用の設定ファイルが生成されます。そして、2つ目のコマンドで、データベースに設定ファイルの情報が反映されることになります。2つ目のコマンド時に、定義されているモデルクラスに対応するテーブルがデータベースに存在しないのであれば、そのテーブルがデータベースに作成されることになります。
したがって、モデルクラスを定義した後に上記コマンドを実行し、その後にウェブアプリを起動してやれば、テーブルへの操作を正常に行うことができるようになります。
ただし、マイグレーションを実施した後にモデルクラスの定義を変更すると、実際にデータベースに存在するテーブルとモデルクラスとの間で差異が発生することになるので注意してください。
テーブルは、マイグレーションを実行したタイミングのモデルクラスの定義にしたがって作成・更新されます。それに対し、ウェブアプリは、ウェブアプリを起動したタイミングのモデルクラスの定義にしたがってテーブルの操作やレコードの操作を実行します。つまり、マイグレーションをした後にモデルクラスの定義を変更すると、実際のテーブルとウェブアプリの想定するテーブルとに差異が発生することになります。
例えば、マイグレーションを実行した後にモデルクラスにフィールドを追加した場合、テーブルはマイグレーション実行時のモデルクラスに基づいて作成されているため、その追加したフィールドはテーブルには存在しないことになります。それに対し、ウェブアプリは、ウェブアプリ起動時のモデルクラスに基づいてテーブルやレコードの操作を行うため、その追加したフィールドも含めたレコードを作成し、それをテーブルに保存したりしようとします。この時、保存しようとしているレコードの持つフィールドと実際のテーブルのフィールドが異なるため、例外が発生することになります。
具体的には、この場合には下記のような例外が発生することになります(ModelApp_student
はテーブル名、science
はデータベースのテーブルに存在しないフィールド名となります)。
django.db.utils.OperationalError: table ModelApp_student has no column named science
したがって、モデルクラスの定義を変更した際には、基本的にはマイグレーションも合わせて実行する必要があるということになります。特にフィールドの追加や削除を行なった場合は、マイグレーションを実行しないとモデルクラスの定義とデータベースのテーブルとで齟齬が発生してウェブアプリが正常に動作しなくなる可能性が高いので注意してください。
参考:データベースの作成方法
テーブルの作成はマイグレーションによって実現できることは理解していただけたのではないかと思いますが、データベース自体の作成はどうやって行えば良いでしょうか?
このデータベース自体の作成方法は、ウェブアプリから使用する DBMS(データベース管理システム)によって異なります。が、Django ではウェブアプリから使用する DBMS として sqlite3
(SQLite)がデフォルトで設定されており、この場合は、マイグレーション実行時や Django 開発用ウェブサーバー起動時等にデータベースが自動的に作成されることになります。
そのため、sqlite3
を使用する場合は、データベースを作成するための手順は不要となります。
また、使用する DBMS が sqlite3
である場合、データベースの実体としてファイルが生成されることになります。このファイルはプロジェクト内に db.sqlite3
という名前で作成されます。そして、データベースにテーブルが作成されたりテーブルにレコードが保存されたりすると、このファイルが更新されていくことになります。
この db.sqlite3
からデータベースに存在するテーブルやレコードを確認するようなことも可能です。ただ、db.sqlite3
自体はバイナリファイルであって、テキストファイルで開いても意味の分からないデータとなっています。なので、db.sqlite3
からテーブルやレコードを確認するためには別途専用のソフトを利用する必要があります。
VSCode を利用されている場合は、プラグインの利用により簡単にデータベース・テーブルの中身を閲覧することができ、その方法は下記ページで解説していますので、詳しく知りたい方は下記ページを参考にしていただければと思います。
【Django/VSCode】データベースのテーブルの中身を表示する別に上記ページで解説している方法にこだわる必要はないのですが、データベースの中身を手軽に確認できる手段を用意しておけば、モデルとデータベースの関連性を確認しながらウェブアプリを開発することができて便利です。
DBMS が sqlite3
の場合に自動的にデータベースが作成される一方で、例えば DBMS に MySQL を利用したりしている場合、別途データベースを作成するための作業が必要となります。Django のウェブアプリから MySQL 利用する方法に関しては、下記ページで紹介していますので、詳しく知りたい方はこちらをご参照いただければと思います。
リレーションを利用してモデルクラス同士を関連付ける
また、モデルを使いこなす上で重要になるのがリレーションとなります。
前述の通り、モデルクラスはデータベースのテーブルに対応しています。基本的には、これらのテーブルは独立したものとなるのですが、モデルクラスに特別なフィールドを持たせることで、2つのモデルクラスの間に、つまり2つのテーブルの間に関連性を持たせることがで可能となります。そして、この関連性をリレーションと呼びます。そして、このリレーションを利用することで、様々なウェブアプリの機能を実現することができるようになります。
例えば Twitter のようなウェブアプリを開発することを考ると、まずユーザーを管理するためのモデルクラスが必要になります。ここでは、このユーザーを管理するモデルクラスを下記の User
クラスとしたいと思います。
class User(models.Model):
name = models.CharField(max_length=100)
さらに、ユーザーはツイートを行うことが可能で、ウェブアプリでツイートを扱うためにはツイートを管理するためのモデルクラスが必要となります。このツイートを管理するためのモデルクラスを下記の Tweet
クラスとしたいと思います。
class Tweet(models.Model):
text = models.TextField(max_length=140)
これらのモデルを定義した上で、ユーザー登録が行われた際に name
にユーザー名がセットされたレコードを User
のテーブルに作成し、ユーザーがツイートした際に text
にツイート内容がセットされたレコードを Tweet
のテーブルに作成するようにウェブアプリを開発すれば、ウェブアプリでユーザーやツイートの管理を行うことができることになります。
ここで「ユーザーのツイート履歴を閲覧する機能」の実現方法について考えたいと思います。これらのテーブルで、この機能は実現できるでしょうか?
実は、この機能は、上記の2つのモデルクラスでは実現することができません。なぜなら、Tweet
のテーブルには、各ツイートがどのユーザーから行われたものかを判断するための情報がありませんし、User
のテーブルにも、各ユーザーがどのツイートを行なったかを判断するための情報がないからです。
要は、2つのモデルクラスが独立しており、一方のテーブルのレコードが他方のテーブルのどのレコードと関連性を持っているのかが分からないため、ユーザーのツイート履歴を閲覧する機能が実現できないのです。
これを解決するのがリレーションとなります。リレーションでは、2つのモデルクラスを関連づけることができ、一方のモデルクラスのレコードから他方のモデルクラスのレコードを辿ることができるようになります。
具体的には、先程の例であれば、Tweet
のテーブルにツイートしたユーザーの id
を管理する user_id
のフィールドを追加し、さらにツイートが行われた際に、user_id
にツイートを行なったユーザーの id
を設定した上でレコードを保存するようにすれば、各レコードからツイートを行なったユーザーを判断することができるようになります。
あとは、Tweet
のテーブルから、user_id
が “ツイート履歴を表示したいユーザーの id
” と一致するレコードを検索して取得し、それを一覧として表示してやれば、特定のユーザーのツイート履歴を表示する機能が完成します。
このように、2つのテーブルのインスタンスを関連付けることを『リレーション』と呼びます。この関連付けによって、一方のテーブルのレコードから他方のテーブルのレコードを辿ることができるようになり、これによって実現できる機能の幅が広がります。
この関連付けに関しても、モデルクラスのクラス変数でフィールドを定義することで実現することができます。ただし、Django ではリレーションを利用するための特別な Field
のサブクラス(リレーションフィールド)が用意されているため、そのサブクラスを利用してフィールドを定義する必要があります。
この Field
のサブクラスは、具体的には OneToOneField
・ForeignKey
・ManyToManyField
であり、例えば上記の例であれば、モデルクラスの定義を下記のように変更することで Tweet
クラスと User
クラスに関連性を持たせ、それぞれのインスタンスを関連付けできるようになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
class Tweet(models.Model):
text = models.TextField(max_length=140)
user = models.ForeignKey(User, on_delete=models.CASCADE)
このリレーションはウェブアプリを開発する上で非常に重要な機能となりますので、次回の連載(下記ページ)の中で詳細な解説を行っていきます。このページを読み終えた後にでも、下記ページを読んでみていただければと思います!
【Django入門7】リレーションの基本ユーザー管理モデルクラスを利用する
ここまで自身でモデルクラスを定義することを前提に解説を進めてきましたが、Django には標準で用意されているモデルクラスもあります。
特に覚えておくと良いのが User
・AbstractUser
・AbstractBaseUser
になると思います。これらは全て Model
のサブクラスですので、データベースにおけるテーブルとして扱うことができます。
これらの特徴はユーザーを管理するモデルクラスである点になります。User
に関してはユーザーを管理する上で必要になる基本的なフィールド(usename
・password
・email
など)が予め用意されていますし、ユーザーの権限設定などの機能も予め用意されているため、ユーザーを管理するために利用すると便利です。
また、AbstractBaseUser
に関しては、ユーザー管理モデルクラスを自身で作成する際に、AbstractUser
はユーザー管理モデルクラスを拡張する際に便利なモデルクラスになります。
特にウェブアプリでは、ユーザー登録やユーザーログインなどを行って利用するものも多いため、ユーザー管理方法を理解しておくことで、幅広いウェブアプリを実現できるようになります。
これらの User
を利用したログインの実現方法については下記ページで、
AbstractUser
を利用したユーザー管理モデルクラスの拡張、AbstractUser
を利用したユーザー管理モデルクラスの作成については下記ページで解説していますので、ユーザー管理を行いたいという方は是非読んでみてください。
スポンサーリンク
掲示板アプリでモデルを利用してみる
では、ここまで説明してきた内容を踏まえて、実際にモデルの利用例を示していきたいと思います。
この Django 入門 に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでフォームを利用してみる で作成したウェブアプリに対してモデルを導入する形で、モデルの利用例を示していきたいと思います。
【Django入門5】フォームの基本より具体的には、今までユーザーを管理するクラスとして User
、コメントを管理するクラスとして Comment
を views.py
に定義して利用してきていましたが、ユーザーを管理するモデルクラス User
とコメントを管理するモデルクラス Comment
を定義し、views.py
に定義したクラスの代わりにこれらのモデルクラスを利用するようにしていきたいと思います。
今までは単なるクラスを利用し、さらにリストでインスタンスを管理するようにしていましたが、上記の様にモデルクラスを導入することで、単なるクラスではなくモデルクラスを利用し、さらにデータベースでインスタンス(レコード)を管理するようになります。
掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリでフォームを利用してみる で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-form
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。ソースコードの変更等を行うのが面倒な場合など、必要に応じて下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-model
モデルクラスの定義
では、まずはモデルクラスとして User
と Comment
を定義していきたいと思います。
前述の通り、既に views.py
に User
と Comment
を定義していますが、これは単なるクラスであってデータベースの管理には向いていないので、モデルクラスとして User
と Comment
を定義し、こちらのモデルクラスの方を利用するようにしていきたいと思います。
User
の定義
ということで、まずはモデルクラス User
を定義しましょう!これも少し前に触れましたが、Django フレームワークには標準でユーザー管理モデル User
が用意されています。こちらを利用すればログイン機能なども実現しやすくなるのですが、今回は新たに自身で User
を定義し、それを利用するようにしていきたいと思います。
また、User
モデルクラスでは、今まで使用していた views.py
の User
と同等のデータを扱えるようにしたいため、名前 (username
) ・メールアドレス (email
) ・年齢 (age
) の3つのフィールドを持つモデルクラスとして定義を行います。ID (id
) に関しては、別途プライマリーキーとなるフィールドを指定しなければ自動的にプライマリーキーのフィールドとして追加されるため、モデルクラスのフィールドとして定義する必要はありません。
さらに、前述の通り、モデルクラスはアプリフォルダ内の models.py
に定義を行います。今回、アプリ名は forum
としていますので、forum
フォルダ内に存在する models.py
にモデルクラスの定義を追記していくことになります。
今回は、下記のように User
を定義したいと思います。
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField()
age = models.IntegerField()
def __str__(self):
return self.username
モデルの CharField
に関しては引数 max_length
の指定(最大文字数)が必須となりますので、上記のように max_length
を 32
に設定しています。
また、__str__
メソッドを定義しており、これにより User
のインスタンスそのものを出力した場合には、username
フィールドに設定されている文字列が出力されることになります。
この __str__
メソッドに関しては下記ページで解説しているため、詳しくは下記ページをご参照いただければと思います。
上記を定義し、マイグレーションを実行すれば、id
と username
と email
と age
のフィールドを持つテーブルがデータベースに作成されることになります(マイグレーションは後で実行しますので、ここでのマイグレーションの実行は不要です)。
Comment
の定義
続いては、モデルクラス Comment
を定義します。
基本的には User
の時と同様で、今まで使用していた views.py
の Comment
と同等のデータを扱えるようにクラスにフィールドを持たせるようにします。
Comment
の場合は本文 (text
)・投稿日 (date
) をフィールドを持たせれば良いので、下記のように models.py
を変更すれば良いことになります。
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField()
age = models.IntegerField()
def __str__(self):
return self.username
class Comment(models.Model):
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.text[:10]
ポイントは date
フィールドになると思います。models.DateTimeField
のインスタンスとして定義したフィールドは日時を扱うことが可能です。そして、models.DateTimeField()
の引数に auto_now_add=True
を指定することで、レコードとしてデータベースに新規作成する際に、自動的にそのタイミングの日時がフィールドの値として設定されることになります。
また、上記を定義し、マイグレーションを実行すれば、id
と text
と date
のフィールドを持つテーブルがデータベースに作成されることになります(マイグレーションは後で実行しますので、ここでのマイグレーションの実行は不要です)。
スポンサーリンク
モデルクラスの利用(views.py
)
続いては、前述で説明した通り、views.py
から User
と Comment
の定義を削除し、これらの代わりに先ほど定義したモデルクラス User
と Comment
を利用するように処理を変更していきます。
views.py
で定義していた User
と Comment
を利用しているのは views.py
で定義した関数とテンプレートファイルになります。
まずは、views.py
を変更していきたいと思います。また、最初に結論を述べておくと、テンプレートファイルの方は変更不要になります。理由は後ほど説明します。
views.py
の変更必要箇所
まず、views.py
では User
と Comment
を定義していましたが、これらは不要になるので削除を行います。その代わり、models.py
で定義された User
と Comment
を利用するために、models.py
から User
と Comment
の import
を行う必要があります。
そして、これらのクラスを利用している箇所を、モデルクラスの User
と Comment
を利用するように変更していきます。といっても、元々利用していたのはコンストラクタくらいで、モデルクラスでもコンストラクタは利用可能であるため、その部分に関する変更はほぼ不要になります。ただし、User
と Comment
の両方において、id
に関してはテーブルへの新規追加時に自動的に採番されるため、 コンストラクタへの id
の指定は不要になります。
また、今までリストでインスタンスを管理していたのですが、ここからはインスタンス(レコード)はデータベースのテーブルで管理されるようになります。そのため、コメントの投稿やユーザーの登録によって新規に作成されたインスタンスの保存は、リストに対してではなくデータベースのテーブルに対して行う必要があります。これは、インスタンスから save
メソッドを実行させることで実現できます。
また、インスタンスを取得する際にはリストからではなくデータベースのテーブルから取得する必要があります。これには、マネージャーを利用してモデルクラスを操作する で紹介したマネージャーを利用します。
変更後の views.py
上記のポイントを踏まえて変更を行なった views.py
は下記のようなものになります。
from django.http import Http404
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm
from .models import User, Comment
def index_view(request):
return redirect(to='comments')
def users_view(request):
users = User.objects.all()
context = {
'users' : users
}
return render(request, 'forum/users.html', context)
def user_view(request, user_id):
user = get_object_or_404(User, id=user_id)
context = {
'user' : user
}
return render(request, 'forum/user.html', context)
def comments_view(request):
comments = Comment.objects.all()
context = {
'comments' : comments
}
return render(request, 'forum/comments.html', context)
def comment_view(request, comment_id):
comment = get_object_or_404(Comment, id=comment_id)
context = {
'comment' : comment
}
return render(request, 'forum/comment.html', context)
def register_view(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
email = form.cleaned_data.get('email')
age = form.cleaned_data.get('age')
user = User(username=username, email=email, age=age)
user.save()
return redirect('users')
else:
form = RegisterForm()
context = {
'form': form
}
return render(request, 'forum/register.html', context)
def post_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
text = form.cleaned_data.get('text')
comment = Comment(text=text)
comment.save()
return redirect('comments')
else:
form = PostForm()
context = {
'form': form,
}
return render(request, 'forum/post.html', context)
変更点に関しては、下記ページの 掲示板アプリでフォームを利用してみる で紹介した views.py
との差分を確認していただくのが一番分かりやすいと思います。
ポイントだけ、ここで簡単に説明していきます。
モデルクラスの import
まず、ポイントの1つ目がモデルクラスの import
になります。先ほど示した views.py
では下記部分で models.py
から User
と Comment
の import
を行なっています。この from .
から始まる import
は Django でよく使用する形式のものになりますので、是非覚えておいてください。
from .models import User, Comment
レコードの取得
ポイントの2つ目がデータベースからのレコードの取得になります。
users_view
や comments_view
においては、それぞれ User
に対応するテーブルと Comment
に対応するテーブルから全レコードの取得を行なっています。これを行なっているのが下記部分になります。
users = User.objects.all()
comments = Comment.objects.all()
objects
は マネージャーを利用してモデルクラスを操作する で紹介したマネージャーであり、このマネージャーからテーブルへの操作を行うことができます。今回は all
メソッドを利用しているので、各テーブルの全レコードのクエリーセットが返却値として得られることになります。前述の通り、filter
メソッドを利用すれば検索条件を指定してクエリーセットを取得することも可能です。
また、user_view
や comment_view
においては、それぞれ User
に対応するテーブルと Comment
に対応するテーブルから特定の id
を持つ1つのレコードの取得を行なっています。
user = get_object_or_404(User, id=user_id)
comment = get_object_or_404(Comment, id=comment_id)
ここで利用している get_object_or_404
は Django フレームワークに用意されたショートカット関数の1つで、第1引数に指定したモデルクラス、つまりテーブルから、キーワード引数で指定する条件に当てはまるレコードを取得する関数となります。
レコードを取得するだけであればマネージャーを利用して objects.get
を実行することでも実現可能ですが、get_object_or_404
の場合は単にレコードを取得するだけでなく、条件に当てはまるレコードが存在しない場合は例外を自動的に発生してくれます。
例えば、元々の views.py
の comments_view
では下記のような処理を行なっており、comment_id
を id
に持つインスタンスがリストに存在するかどうかを判断し、存在しない場合は例外を発生させ、存在する場合のみインスタンスを取得するような処理を実装していました。これと同様の処理を1つの関数で実現するのが get_object_or_404
となります(get_object_or_404
の場合は、インスタンスの取得先はリストではなくデータベースになります)。
def comment_view(request, comment_id):
if comment_id > len(comments) and comment_id < 1:
raise Http404('Not found comment')
comment = comments[comment_id - 1]
〜略〜
レコードの新規作成
最後のポイントはレコードの新規作成です。
register_view
と post_view
では User
のテーブルに対するレコードの新規作成および Comment
のテーブルに対するレコードの新規作成をそれぞれ行なっています。
今回モデルクラスを利用するようになりましたが、モデルクラスの使用前後で基本的な処理の流れは変わりません。つまり、下記の流れは今まで通りになります。
- フォームから送信されてきたデータを受け取る
- そのデータに基づいてインスタンスを生成する
- そのインスタンスの保存を行なう
ただし、今まではインスタンスの保存先がリストであったのに対し、今回はインスタンスをテーブルに対して保存することになるため、この保存処理を、インスタンスに save
メソッドを実行させるように変更しています。ここまで何回も説明してきた通り、モデルクラスのインスタンスに save
メソッドを実行させれば、そのモデルクラスに対応するテーブルにレコードとして保存が行われることになります。
また、User
のインスタンスを保存する際には自動的に id
フィールドに、Comment
のインスタンスを保存する際には自動的に id
フィールドと date
フィールドに値が設定されることになります。
さらに、モデルクラスを利用する前は単に変数(リスト)にインスタンスを追加することで保存を行なっていましたが、今回からはデータベースのテーブルにレコードとして保存されるようになりますので、ウェブアプリを再起動したような場合でも、ウェブアプリが動作している PC 等を再起動したような場合でも、レコードはデータベースのテーブルに残り続けることになります。
モデルクラスの利用(テンプレートファイル)
上記のように views.py
を変更することで、コンテキストにセットされるデータも自然と変化することになります。具体的には、単なるクラスのインスタンスがモデルクラスのインスタンスになり、単なるクラスのインスタンスのリストがクエリーセットに変化することになります。
なので、テンプレートファイルから参照される変数も変化することになるのですが、今回の場合はテンプレートファイルの変更は不要となります。
テンプレートファイルからは、参照するインスタンスのデータ属性にアクセスしていますが、今回 models.py
に定義した User
や Comment
は、元々 views.py
に定義していたクラスと同じデータ属性(フィールド)を持つように定義しています。そのため、変更前と同じデータ属性にアクセスすることが可能であり、テンプレートファイルの変更は不要となります。
また、リストがクエリーセットに変化しますが、両方ともイテラブルなオブジェクトであり、どちらもテンプレートタグの for
等を利用することが可能です。なので、コンテキストにセットされるデータは変化するものの、テンプレートファイル側の変更は不要となります。
ただし、今回はテンプレートファイルの変更が不要となるようにモデルクラスを定義していますが、変更前と異なるデータ属性を持つようにモデルクラスを定義する場合は、それに合わせてテンプレートファイル側の変更も必要となります。
動作確認
ソースコードの変更の解説は以上となります。
最後に動作確認を行なっておきましょう!
マイグレーションの実行
今までは最初に Django 開発用ウェブサーバーを起動してから動作確認を行なっていましたが、今回からモデルを利用するようになったため、最初の手順としてモデルクラスをデータベースに反映するためのマイグレーションが必要となります。このマイグレーションにより、定義したモデルクラスに基づくテーブルがデータベースに作成されることになります。
ということで、最初にマイグレーションを行なっておきましょう!このマイグレーションは、プロジェクトフォルダの直下、つまり、manage.py
が存在するフォルダで下記コマンドを実行することで行うことができます。
% python manage.py makemigrations
% python manage.py migrate
もし、モデルクラスの定義に問題がある場合は、上記のいずれかのコマンド実行中に例外が発生することになります。また、views.py
等に問題があるような場合も上記のコマンド実行中にエラー・例外が発生することになりますので注意してください。
正常に2つのコマンドが終了した場合は、下記のようなメッセージが表示されることになります。
% python manage.py makemigrations Migrations for 'forum': forum/migrations/0001_initial.py - Create model Comment - Create model User % python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, forum, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK 〜略〜 Applying forum.0001_initial... OK Applying sessions.0001_initial... OK
そして、定義したモデルクラスに基づくテーブルがデータベース内に作成されます。今回は2つのテーブルが作成されることになり、1つ目のテーブルが forum_user
で、User
に対応したテーブルとなります。
User
のインスタンスを生成し、そのインスタンスに save
メソッドを実行させた際には、この forum_user
のテーブルにレコードが保存されることになります。
2つ目のテーブルが forum_comment
で、Comment
に対応したテーブルとなります。
Comment
のインスタンスを生成し、そのインスタンスに save
メソッドを実行させた際には、この forum_comment
のテーブルにレコードが保存されることになります。
開発用ウェブサーバーの起動
マイグレーションが完了した後は、いつも通り Django 開発用ウェブサーバーの起動を行います。マイグレーションを実行した時と同じフォルダで下記コマンドを実行すれば、Django 開発用ウェブサーバーが起動し、ウェブアプリがリクエストを受け取ってくれるようになります。
% python manage.py runserver
ページ表示の確認
次はウェブブラウザを開き、アドレスバーに下記 URL を指定します。
http://localhost:8000/forum/comments/
そうすると、下の図のようにコメント一覧のページが表示されると思います。今までは予め用意しておいたリストの全要素をコメント一覧として表示していましたが、今回からデータベースのテーブル(forum_comment
)の全レコードが、このコメント一覧のページに表示されることになります。
ただし、まだテーブルは作成したばかりで空なので、コメントは1つも表示されないはずです。テーブルにレコードを追加するために、次はナビゲーションバーの コメント投稿
リンクをクリックし、コメント投稿フォームからコメントを投稿してみてください。
コメントの投稿が完了すれば再びコメント一覧ページが表示され、先ほど投稿したコメントの本文が表示されるようになっていることが確認できると思います。
コメントが表示されるようになったのは、コメントの投稿によりテーブル(forum_comment
)にレコードが追加されたからです。
また、コメント一覧における本文部分はリンクになっており、このリンクをクリックすることで、今までと同様にコメントの詳細も確認できると思います。前述の通り、投稿日はコメントを投稿した日に自動的に設定されます。
同様の操作を繰り返せば、コメント一覧に表示されるコメントがどんどん増えていくことも確認できると思います。
次は、ナビゲーションバーの ユーザー一覧
リンクをクリックしてみてください。これによりユーザー一覧が表示されると思いますが、今回もまだテーブル(forum_user
)が空なのでユーザーは1つも表示されないはずです。
先ほどと同様に、ナビゲーションバーの ユーザー登録
リンクをクリックしてユーザーを登録すれば、再度ユーザー一覧を表示した際に表示されるユーザーが増えていることを確認できると思います。
下記ページの 掲示板アプリでフォームを利用してみる で紹介したウェブアプリでも、コメントの投稿やユーザーの登録を行うことはできていましたが、単にリストに要素を追加していただけなのでウェブアプリを一度終了すると追加した要素は削除されてしまっていました。
【Django入門5】フォームの基本ですが、今回からモデルを導入し、データベースにデータを保存するようになったため、ウェブアプリを終了しても追加したレコードの情報は残り続けることになります。逆に言えば、レコードを削除する際にはデータベースの操作を行なってレコードを削除する手順が必要になったことになります。
今回は、レコードの削除の例は示しませんが、レコードの削除は例えばインスタンスに delete
メソッドを実行させることで実現可能です。例えば、コメントやユーザーの一覧にリンクやボタンを追加し、それらがクリックされた際に対応するコメントやユーザーを削除するような動作を実現してみても面白いと思います。
レコードの削除の例だけでなく、いろんな拡張方法が考えられますので、是非ご自身でウェブアプリの拡張にも挑戦してみていただければと思います!
スポンサーリンク
まとめ
このページでは、Django のモデルについて解説しました!
モデルはデータベースの管理を行う仕組みであり、モデルクラスを models.py
に定義して利用することになります。
また、モデルクラス自体はデータベースのテーブルに対応しており、モデルクラスのインスタンスがテーブルのレコードに対応しています。
データベースの管理と聞くと地味なイメージを持たれるかもしれませんが、ウェブアプリにおいて扱うデータはウェブアプリの特徴を決める重要な要素であり、それを決定するのがモデルとなります。そのため、モデルクラスの定義によってウェブアプリの仕上がりが決まるといっても過言ではありません。自身が開発したいウェブアプリで扱う必要のあるデータを考え、それに基づいて適切にモデルクラスを定義することが重要です。
また、モデルクラスをデータベースに反映するためにはマイグレーションが必要となります。これについても是非頭に入れておいてください!
さて、今回モデルについて説明を行いましたが、モデルを使いこなす上で重要になるのが リレーションを利用してモデルクラス同士を関連付ける で簡単に説明させていただいたリレーションになります。これにより、独立していたモデルクラス同士に関連性を持たせることができ、それによって様々な機能を実現することができるようになります。下記ページの次回の連載では、このリレーションについて解説していきたいと思います!
【Django入門7】リレーションの基本