【Django入門6】モデルの基本

モデルの解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django におけるモデル(Model)について解説していきます。

Django で開発するウェブアプリの基本構造は MTV モデルとなっています。そして、この M の部分がモデルとなります。このように、Django で開発するウェブアプリの基本構造の1つであり、モデルはウェブアプリにおいて重要な存在となります。

モデルがMTVのMであることを示す図

ただ、MTV という言葉やモデルの存在を知っている方でも、モデルがどのような役割を果たすものであるかについてはピンときていない方も多いのではないかと思います。

そんな方に向け、このページでは、Django におけるモデルの基本について、特に Django 初心者の方向けに詳しく・分かりやすく解説していきたいと思います。

モデルとは?

まず、Django におけるモデルとは、ウェブアプリを構成する Python スクリプトからデータベースを操作するための仕組みとなります。もっと簡単に言えば、データベースの操作を実現するクラスとなります。

さらに、モデルはデータベースで扱う(データベースに保存する)データの種類を定義する役割も持ちます。

モデルとは「データベースの操作を実現するクラス」

ウェブアプリには、データベースを操作してデータの保存や取得を行うものが多いです。

MEMO

ここからは、Twitter などの例を用いてモデルの重要性について解説していきますが、Twitter の作りや動作はあくまでも私の予想・想像のものとなるので注意してください

あくまでもモデルの重要性を理解していただくことを目的に、実際のウェブアプリの例として Twitter を使用させていただいています

ウェブアプリにおけるデータベース操作の必要性

例えば Twitter であれば、「ツイート」や「いいね!」や他のユーザーの「フォロー」等を行うことができます。これらを行なった際、これらの「ツイート」された情報のデータや「いいね!」した情報のデータ、ユーザーを「フォロー」した情報のデータが Twitter アプリに送信され、それらのデータが Twitter アプリからデータベースに保存されるようになっているのではないかと思います。

ウェブアプリがデータベースにデータを保存する様子

さらに、これらのデータベースに保存されているデータは後から取得して表示することが可能です。例えば Twitter では自身や他のユーザーの「ツイート」の履歴や「いいね!」した履歴を表示することが可能となっています。また、自身が「フォロー」しているユーザーの情報も表示することができます。これが可能なのは、前述の通り、データベースにデータが保存されているからで、この保存されているデータを取得することで、後から様々な情報を表示できるようになっています。

ウェブアプリがデータベースからデータを取得する様子

ここでは Twitter を例に挙げましたが、保存されるデータの種類は異なるものの、インスタグラムや Facebook など、他のウェブアプリにおいても大体こんな感じでデータベースにデータが保存されていることが想像できるのではないかと思います。

このように、ウェブアプリではデータベースにデータを保存したり、データベースからデータを取得することで様々な機能を実現しているものが多いです。

モデルにおけるデータベース操作

そして、これらのデータの保存やデータの取得など、データベースに対する操作を行うのがモデルの役割の1つとなります。

このモデルでのデータベースに対する操作の特徴は、メソッドの実行によって操作が実現できる点になります。

通常であれば、データベースに対する操作は DBMS と呼ばれるデータベース管理システムに対し、クエリ(依頼)を送信することで実現されることになります。そのため、データベースの操作を行いたい場合、その操作に対応したクエリを作成する必要があり、この際にクエリや SQL 等の知識が必要となります。

クエリを発行してデータベースを操作する様子

ですが、Django では、モデルを利用することでデータベースに対する操作をメソッドの実行により実現することができます。これらのメソッドが、メソッドや引数等に応じたクエリを生成し、それを DBMS に対して発行してくれます。したがって、ウェブアプリの開発者はクエリの知識がなくても Python でのメソッドの実行の仕方を知っていれば、データベースの操作を実現することができることになります。

メソッドを実行してデータベースを操作する様子

補足しておくと、前述の通りウェブアプリを単に開発するだけであればモデルやメソッドの利用ができれば十分です。ですが、特にウェブアプリの処理速度・パフォーマンスの向上を図る際にはデータベースやクエリの知識が役立ちますので、これらの知識があるに越したことはありません。

特に、Django ではクエリの知識なしでウェブアプリが開発できてしまうので N + 1 問題という問題が発生しやすいです。この辺りの解説は Django 入門13の下記のページで解説していますので、まずはクエリに関しては意識せずにウェブアプリ開発に取り組んでいただくので良いと思います。

DjangoにおけるN+1問題の解説ページアイキャッチ 【Django入門13】N+1問題とselect_related・prefetch_relatedでの解決

スポンサーリンク

ウェブアプリを特徴づけるのは「モデル」

このデータベースに関して特に重要なポイントは、データベースに保存するデータの種類によってウェブアプリの特徴が決まるという点になります。ウェブアプリで扱うことのできるデータは、基本的には、その時にユーザー(クライアント)から受信したデータとデータベースに既に保存されているデータのみとなります。

ウェブアプリが利用可能なデータを示す図

特に、後からデータを利用したい場合は、基本的にはそのデータをデータベースに保存しておく必要があります。これは別に Django やウェブアプリに限った話ではなく、他のプログラミングにおいても同様ですよね。後から利用したいデータは、変数やファイル等に保存しておく必要があります。特にウェブアプリの場合は、その保存先がデータベースとなります。

さらに、ウェブアプリではデータの利用やデータの加工によって実現される機能が多いです。そして、扱うデータの種類によって、ウェブアプリで実現可能な機能も変化することになります。そのため、「データベースに保存するデータの種類」はウェブアプリの機能の実現の面で非常に重要な要素となります。

例えば Twitter であれば、ユーザーが「いいね!」した情報のデータが保存されることで、自身のツイートに「いいね!」した人数を確認することができるようになっています。自身のツイートに「いいね!」してくれた人数を確認することが好きな方も多いと思いますし、この機能は Twitter の特徴の1つであると言っても良いと思います。

ウェブアプリの機能の1つを説明する図

この機能が実現できるのは、各ツイートに “「いいね!」をした人数を判断可能とするデータ” がデータベースに保存されているからです。例えば、ツイートに対して「いいね!」をした人数そのものや、ツイートに対して「いいね!」をしたユーザーの情報を保存しておくようにしておけば、後から「いいね!」された回数を表示することができます。

ウェブアプリの機能を実現するためにデータベースにデータが保存される様子

ですが、こういったデータが保存されていなかった場合、後から自身のツイートに「いいね!」してくれた人数を表示することができず、上記のような機能はウェブアプリで実現できないことになります。つまり、データベースに保存するデータの種類を減らせば、ウェブアプリの特徴も減ることになるのです。

このように、データベースに保存するデータの種類はウェブアプリの特に機能的な特徴を決定する重要な要素となっています。

データベースに保存するデータの種類がウェブアプリの特徴を決める様子

そして、このデータベースに保存するデータの種類を決定するのもモデルの役割となります。つまり、ウェブアプリの機能的な特徴を決定するのはモデルと言っても過言ではありません。

モデルの役割の1つを示す図

そのため、もしかしたらモデルに地味な印象を抱いている方もおられるかもしれませんが、実はウェブアプリの特徴の骨格を形成する非常に重要な役割を担うのがモデルとなります。

ウェブアプリで扱いたいデータがあったとしても、そのデータ、もしくはそのデータの元となるデータが保存されていなければ、そのウェブアプリではそのデータを扱うことができません。例えば、健康管理ウェブアプリで日々の BMI を表示できるようにウェブアプリを開発しようとしているのに、データベースに保存するデータが身長のみであれば、日々の BMI を表示するような健康管理ウェブアプリを開発することはできないのです(BMI の算出には身長だけでなく体重が必要)。

あなたが開発してみたいウェブアプリはどのようなものでしょうか?そのウェブアプリはデータの保存を行う必要がありますか?

データの保存を行う必要がある場合、そのウェブアプリの機能を実現するためにデータベースへの保存が必要なデータの種類を考えそれらを保存できるように適切にモデルを実装していく必要があります。

モデルを実装するファイル

そして、このモデルの実装先となるファイルは models.py になります。この models.py にモデルを作成(定義)していくことになります。

Django でウェブアプリを開発する際には、下記のように startproject コマンドと startapp コマンドを実行することになりますが、models.py は後者の startapp コマンドにより自動的に生成されるファイルとなります。

% django-admin startproject プロジェクト
% python manage.py startapp アプリ

そして、この models.pystartapp で生成されるアプリ毎に用意されることになり、必要に応じて各アプリ毎に実装を行なってモデルを作成していく必要があります。

アプリごとにmodels.pyが存在することを示す図

また、前述の通りモデルの定義先のファイルは 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 というフィールドが追加されていますが、このフィールドはプライマリーキーであり、プライマリーキーに設定するフィールドの指定を行わなかった場合に自動的に追加されるフィールドとなります。

Studentクラスの定義に基づいたテーブルを示す図

このように、モデルクラスそのものがテーブルとなり、クラスのクラス変数がフィールドの定義を行うことになります。したがって、Django でのウェブアプリ開発者は、テーブルで扱いたいデータの種類に応じて適切にモデルクラスの定義を行う必要があります。

また、各テーブルには名前が設定されることになり、この名前は アプリ名_クラス名 となります(ただし、テーブル名においてはクラス名が全て小文字となります)。つまり、上記のモデルクラスの場合、アプリ名を例えば ModelApp とすれば、テーブル名は ModelApp_student となります。

MEMO

ここで一点補足しておくと、モデルクラスのフィールドの定義に使用する Field のサブクラスは、前回の連載の下記ページでの解説で紹介したフォームクラスのフィールドの定義に使用する Field のサブクラスとは異なるので注意してください 

これらは異なるモジュールで定義されており、そのため、Field のサブクラスを利用するために import するモジュールも異なることになります

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

インスタンス:レコード

モデルクラスがテーブルであるのに対し、モデルクラスのインスタンスは、そのモデルクラスのテーブルに対応するレコードとなります。厳密に言えば、モデルクラスのインスタンス = レコード + α です。モデルクラスのインスタンスは各フィールドのデータの値以外にもメソッドやメタデータ等を持っています。また、インスタンスというよりもオブジェクトと言う方がしっくりくる方もおられるかもしれませんが、クラスと対比するためにインスタンスという言葉を使って説明していきます。

モデルクラスのインスタンスとレコードの関係図

モデルクラスのインスタンスでは、クラスと同じフィールドをデータ属性として持つことになります。そして、このデータ属性からレコードの各フィールドに対してアクセスすることが可能です。このフィールドには具体的なデータを設定することができますが、各フィールドに設定可能なデータの型はクラス変数での定義によって異なることになります。

各フィールドにインスタンスのデータ属性からアクセス可能であることを示す図

例えば、前述の Student クラスの場合、このクラスのインスタンスからはデータ属性として namemathjapanese (+ id)が利用可能で、これらの値を読み込んだり、値を設定したりすることが可能となります。ただし、name には文字列、mathjapanese には整数のみが設定可能となります。なぜなら、そのようにモデルクラスのクラス変数に Filed のサブクラスを設定しているからです。

さらに、モデルクラスには、レコードへの操作を行うメソッドが用意されています。そして、これらのメソッドはインスタンスから実行可能であり、このメソッドの利用によってテーブルに対してレコードを新規作成したり、レコードを削除したりすることができます。

例えば、前述の Student クラスのインスタンスを利用して下記のような処理を行なった場合の動作について考えてみましょう。

レコードの作成例
student = Student()
student.name = '山田太郎'
student.math = 95
student.japanese = 84
student.save()

まず1行目で Student クラスのコンストラクタが実行されてインスタンスが生成され、そのインスタンスを変数 student が参照することになります。このインスタンスは、前述の通り Student のテーブルのレコードとして扱われ、student からは namemathjapanese のフィールドにデータの設定を行うことが可能です。ただし、まだこのレコードは新規作成したばかりなので、各フィールドには初期値が設定されていることになります。

コンストラクタを実行してレコードを生成する様子

次に、2行目から4行目で各フィールドに対してデータが設定されることになります。このように、インスタンス毎に各フィールドにデータを設定することが可能であり、これはレコードの各フィールドにデータを記録するのと同じ操作になります。

インスタンスからレコードのフィールドにデータを設定する様子

さらに、5行目では studentsave というメソッドを実行しており、これによりレコードが Student のテーブルに保存されることになります。この例の場合、コンストラクタによって新規作成されたインスタンスが save メソッドを実行しているため、テーブルに対してレコードの新規作成が行われることになります(レコードの新規作成時には、プライマリーキーである id が自動的に設定されることになります)。

インスタンスのsaveメソッドによりテーブルにレコードが保存される様子

この例からも分かるように、モデルクラスのインスタンスはテーブルにおけるレコードとして扱うことができます。

また、上記のようなインスタンスの生成やインスタンスからのメソッドの実行は、基本的にはビュー(views.py)から行うことになります。

モデルクラスの定義

次はモデルクラスの定義方法について解説していきます。

スポンサーリンク

Model のサブクラスとしてクラスを定義

クラス:テーブル でも簡単に示しましたが、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 を指定することで保存可能な文字列の最大長という制約をフィールドに加えることができます。

max_lengthの指定
from django.db import models

class TestModel(models.Model):
    # nameフィールドを32文字以下に制限する
    name = models.CharField(max_length=32)

また、様々な Field のサブクラスのコンストラクタでは unique 引数を指定することが可能で、unique=True を指定することで同じテーブルのレコード間における、そのフィールドのデータの重複不可という制約を追加することもできます。

uniqueの指定
from django.db import models

class TestModel(models.Model):
    # emailフィールドの重複を禁止する
    email = models.EmailField(unique=True)

また、これも null=True を指定することで、そのフィールドへのデータの保存が必須となります。この場合、そのフィールドが空の状態ではデータベースに保存することできません。この引数に関してはデフォルト設定が null=True となっているため、そのフィールドが空の状態でもデータベースに保存できる様にするためには null=False を指定する必要があります。

nullの指定
from django.db import models

class TestModel(models.Model):
    # ageフィールドの空白を許可する
    age = models.IntegerField(null=True)

さらに、primary_key=True を指定すれば、そのフィールドがプライマリーキーとして扱われることになります。プライマリーキーのフィールドはレコード間でデータが重複することは許されませんので、自然と unique=True を指定した場合と同じ制約が追加されることになります。

primary_keyの指定
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 のサブクラスのコンストラクタの記述を行う際に指定可能な引数一覧を示してくれるようになっています。

こういった情報を参照しながら、フィールドへの引数指定を行ってフィールドのカスタマイズを試してみると良いと思います。

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 メソッドが実行され、通常のデータベースへの保存処理も行うことができます。

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__ メソッドを定義することで、モデルクラスのインスタンスそのものが出力された際の出力文字列を定義することができます。

__str__メソッドの定義
from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name

__str__ メソッドについては詳細を下記ページで解説していますので、詳しくは下記ページをご参照ください。

モデルの__str__メソッドの説明ページアイキャッチ 【Django】モデル(Model)の__str__メソッドとは?

モデルを使いこなすための知識

ここまでの説明で、モデルがどういったものであるのかについてはイメージが湧くようになったのではないかと思います。

ここからは、もう一歩踏み込んで、モデルを使いこなすために必要な知識について説明をしていきます。

マネージャーを利用してモデルクラスを操作する

さて、先ほど インスタンス:レコード でレコードをテーブルに新規作成する例を示しました。このレコードの新規作成は、インスタンスにメソッドを実行させることで実現することができましたね!

このレコードの新規作成は、レコードの削除や更新・表示に比べると手順が簡単です。なぜなら、テーブルからのレコードの取得が不要だからです。新規作成の場合、コンストラクタでインスタンスを生成し、それに save メソッドを実行させれば良いだけであるため、事前にテーブルからレコードを取得しておく必要はありません。

それに対し、レコードの表示を行うことを考えた場合、事前にテーブルから所望のレコードを取得しておく必要があります。

レコードを表示したい場合、テーブルからレコードを取得しておく必要があることを示す図

レコードを取得する前には当然そのレコードを使用することができないため、新規作成時のようにレコードに対応するインスタンスにメソッドを実行させてレコードを取得するようなことはできません。

このような場合に便利なのがマネージャーとなります。このマネージャーは、モデルクラスに対して、つまりテーブルに対して操作を行うためのオブジェクトです。各モデルクラスはこのマネージャーを持っており、objects という名前でモデルクラスから利用することができます。

マネージャー(objects)の説明図

モデルクラスのインスタンスからはメソッドの実行によってレコードを操作することができるのに対し、

インスタンスからレコードの操作が可能であることを示す図

モデルクラスの objects からはメソッドの実行によってテーブルを操作することができます。

マネージャーからテーブルの操作が可能であることを示す図

また、objects はインスタンスではなくモデルクラスから利用するものとなります。インスタンスが無くても利用することができるため、この objects にメソッドを実行させることで、テーブルからレコード(インスタンス)の取得を行うようなことが可能となります。

取得されるレコードは、そのモデルのインスタンス、もしくはモデルのインスタンスの集合(QuerySet)となります。例えば、下の図のような Student クラスのテーブルについて考えてみましょう!

マネージャーによるレコードの取得を説明するために用いるテーブルの図

このテーブルから math95 である生徒のレコードの取得は、下記のように objects から filter メソッドを実行させることで実現することができます。

レコードの取得例
students = Student.objects.filter(math=95)

filter メソッドは、モデルクラスに対応するテーブルから引数で指定した条件に該当するレコード(インスタンス)の集合を取得するメソッドになります。下記では Student.objects から filter メソッドを実行しているため、Student のテーブルから math フィールドが 95 のレコードをまとめた QuerySet が取得されることになります。

filterメソッドによりテーブルからレコードを検索して取得する様子

この QuerySet はリスト等のようにイテラブルな型であり、リストなどのように for ループを利用して QuerySet 内の各要素を取得し、その各要素1つ1つに対して処理を行うことが可能です。そして、QuerySet の各要素はレコード、つまりはレコードの取得元のモデルクラスのインスタンスとなります。

filterメソッドで取得されたQuerySetがモデルクラスのインスタンスであることを示す図

したがって、上記の filter メソッドを実行して取得した QuerySet を利用すれば、次のように math=95 を満たすレコード(インスタンス)の情報を表示することが可能となります。

取得したレコードの利用例1
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つで、この場合はメソッドの返却値をそのままインスタンスとして利用できます。

取得したレコードの利用例2
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 メソッド等のレコード保存のための処理を行わない限りテーブルにレコードは新規作成されません。

objectsを利用したレコード作成
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つ目のコマンドで、データベースに設定ファイルの情報が反映されることになります。この時、定義されているモデルクラスに対応するテーブルがデータベースに存在しないのであれば、そのテーブルがデータベースに作成されることになります。

マイグレーションによってmodels.pyに定義したモデルクラスがデータベースに反映される様子

したがって、モデルクラスを定義した後に上記コマンドを実行し、その後にウェブアプリを起動してやれば、テーブルへの操作を正常に行うことができるようになります。

ただし、モデルクラスの定義を後から変更してフィールドの追加や削除を行なった場合、実際にデータベースに存在するテーブルとモデルクラスとの間で差異が発生することになります。あくまでもウェブアプリはモデルクラスの定義に一致したテーブルがデータベースに存在することを前提として動作しますので、この場合もウェブアプリからレコードの保存を行おうとすると、ウェブアプリが生成したレコード(モデルクラスのインスタンス)とデータベースに存在するテーブルとでフィールドの種類や数が一致せず、例外が発生する可能性があります。

モデルクラスとデータベースのテーブルとの間に差異があってレコードの保存に失敗する様子

例えば、モデルクラスに新たなフィールドを持たせたのにデータベースにそれを反映しなかった場合、レコード保存時に下記のような例外が発生することになります(ModelApp_student はテーブル名、science はデータベースのテーブルに存在しないフィールド名となります)。

django.db.utils.OperationalError: table ModelApp_student has no column named science

つまり、ウェブアプリは常に最新の models.py のモデルクラスの定義に従って動作を行いますが、データベースに存在するテーブルはマイグレーションを行なったタイミングの models.py のモデルクラスの定義に従ったものとなります。

ですので、モデルクラスの定義を変更した際には、基本的にはマイグレーションを実行する必要がありますので、この点については注意してください。特にフィールドの追加や削除を行なった場合は、マイグレーションを実行しないとモデルクラスの定義とデータベースのテーブルとで齟齬が発生してウェブアプリが正常に動作しなくなる可能性が高いです。

参考:データベースの作成方法

テーブルの作成はマイグレーションによって実現できることは理解していただけたのではないかと思いますが、データベース自体の作成はどうやって行えば良いでしょうか?

このデータベース自体の作成方法は、ウェブアプリから使用する DBMS(データベース管理システム)によって異なります。が、Django ではウェブアプリから使用する DBMS として sqlite3(SQLite)がデフォルトで設定されており、この場合は、マイグレーション実行時や Django 開発用ウェブサーバー起動時等にデータベースが自動的に作成されることになります。

そのため、sqlite3 を使用する場合は、基本的にデータベースの作成忘れは発生しません。

また、使用する DBMS が sqlite3 である場合、データベースの実体としてファイルが生成されることになります。このファイルはプロジェクト内に db.sqlite3 という名前で作成されます。そして、データベースにテーブルが作成されたりテーブルにレコードが保存されたりすると、このファイルが更新されていくことになります。

db.sqlite3がプロジェクト内に生成される様子

また、この db.sqlite3 からデータベースに存在するテーブルやレコードを確認するようなことも可能です。ただ、db.sqlite3 自体はバイナリファイルであって、テーブルやレコードを確認するためには別途専用のソフト等を利用する必要もあります。VSCode を利用されている場合は、プラグインの利用により簡単にデータベース・テーブルの中身を閲覧することができ、その方法は下記ページで解説していますので、詳しく知りたい方は下記ページを参考にしていただければと思います。

VSCodeでデータベースのテーブルの中身を表示する方法の解説ページアイキャッチ 【Django/VSCode】データベースのテーブルの中身を表示する

別に上記ページで解説している方法にこだわる必要はないのですが、データベースの中身を手軽に確認できる手段を用意しておけば、モデルとデータベースの関連性を確認しながらウェブアプリを開発することができて便利です。

DBMS が sqlite3 の場合に自動的にデータベースが作成される一方で、例えば DBMS に MySQL を利用したりしている場合、別途データベースを作成するための作業が必要となります。Django のウェブアプリから MySQL 利用する方法に関しては、下記ページで紹介していますので、詳しく知りたい方はこちらをご参照いただければと思います。

DjangoでMySQLを利用するための手順の解説ページアイキャッチ 【Django】DjangoでMySQLを利用する

リレーションを利用してモデルクラス同士を関連付ける

また、モデルを使いこなす上で重要になるのがリレーションとなります。

前述の通り、モデルクラスはデータベースのテーブルに対応しており、モデルクラス同士に関連性を持たせることも可能です。この関連性をリレーションと呼びます。このリレーションを利用することで、ウェブアプリで様々な機能を実現することが可能となります。

例えば Twitter のようなウェブアプリの例で考えてみると、まずユーザーを管理するためのモデルクラスが必要になります。ここでは、このユーザーを管理するモデルクラスを下記の User クラスとしたいと思います。

Userクラス
class User(models.Model):
    name = models.CharField(max_length=100)

さらに、ユーザーはツイートを行うことができ、そのツイートを管理するためのモデルクラスが必要になります。このツイートを管理するためのモデルクラスを下記の Tweet クラスとしたいと思います。

Tweetクラス
class Tweet(models.Model):
    text = models.TextField(max_length=140)

ユーザー登録が行われた際に name にユーザー名がセットされたレコードが User のテーブルに作成され、ユーザーがツイートした際に text にツイート内容がセットされたレコードが Tweet のテーブルに作成されるようにしていけば、ユーザーやツイートの管理を行うことができることになります。

ユーザーとツイートを管理するテーブル

ここで「ユーザーのツイート履歴を閲覧する機能」について考えたいと思います。この機能は、上記の2つのモデルクラスでは実現することができません。なぜなら、Tweet のテーブルには、各ツイートがどのユーザーから行われたものかを判断するための情報がありませんし、User のテーブルにも、各ユーザーがどのツイートを行なったかを判断するための情報がないからです。

要は、ユーザーのツイート履歴を閲覧する機能が実現できないのは、2つのモデルクラスが独立しているからになります。

2つのテーブルが独立していることを示す図

これを解決するのがリレーションとなります。リレーションでは、2つのモデルクラスを関連づけることができ、一方のモデルクラスのインスタンス(レコード)から他方のモデルクラスのインスタンスを辿ることができるようになります。

具体的には、先程の例であれば、Tweet のテーブルにツイートしたユーザーの id を管理する user_id のフィールドを追加し、さらにツイートが行われた際に、user_id にツイートを行なったユーザーの id を設定した上でレコードを保存するようにすれば、各レコードからツイートを行なったユーザーを判断することができるようになります。

user_idフィールドでツイートしたユーザーを管理する様子

あとは、user_id がツイート履歴を表示したいユーザーの id と一致するレコードを検索して取得し、それを一覧として表示してやれば特定のユーザーのツイート履歴を表示する機能が完成します。 

このように、2つのモデルクラス間で関連性を持たせることをリレーションと呼びます。リレーションを設定しておくことで、一方のモデルクラスのインスタンスから他方のモデルクラスのインスタンスを辿ることができるようになり、開発できるウェブアプリの幅が広がります。

このリレーションの設定も、モデルクラスのクラス変数でフィールドを定義することで実現することができます。ただし、Django ではリレーションを設定するための特別な Field のサブクラスが用意されているため、そのサブクラスを利用してフィールドを定義する必要があります。

このサブクラスは、具体的には OneToOneFieldForeignKeyManyToManyField であり、例えば上記の例であれば、モデルクラスの定義を下記のように変更することで 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)

このリレーションは重要なので、次回の連載の中で詳細な解説を行うようにしています。具体的には、下記ページで解説を行なっており、ここでリレーションの設定を行う際に利用する OneToOneFieldForeignKeyManyToManyField の違いなども説明していますので、詳しくは下記ページの解説を読んでみていただければと思います!

Django のリレーションについての解説ページアイキャッチ 【Django入門7】リレーションの基本

ユーザー管理モデルクラスを利用する

ここまで自身でモデルクラスを定義することを前提に解説を進めてきましたが、Django には標準で用意されているモデルクラスもあります。

特に覚えておくと良いのが UserAbstractUserAbstractBaseUser になると思います。これらは全て Model のサブクラスですので、データベースにおけるテーブルとして扱うことができます。

これらの特徴はユーザーを管理するモデルクラスである点になります。UserAbstractUser に関してはユーザーを管理する上で必要になる基本的なフィールド(usenamepasswordemail など)が予め用意されていますし、ユーザーの権限設定などの機能も予め用意されているため、ユーザーを管理するために利用すると便利です。

また、AbstractBaseUser に関しては、ユーザー管理モデルクラスを自身で作成する際に、AbstractUser はユーザー管理モデルクラスを拡張する際に便利なモデルクラスになります。

特にウェブアプリでは、ユーザー登録やユーザーログインなどを行って利用するものも多いため、ユーザー管理方法を理解しておくことで、幅広いウェブアプリを実現できるようになります。

これらの User を利用したログインの実現方法については下記ページで、

Djangoでのログイン機能の実現方法解説ページアイキャッチ 【Django】ログイン機能の実現方法(関数ベースビュー編) Djangoでクラスベースビューでログインを実現する方法の解説ページアイキャッチ 【Django】ログイン機能の実現方法(クラスベースビュー編)

AbstractUser を利用したユーザー管理モデルクラスの拡張、AbstractUser を利用したユーザー管理モデルクラスの作成については下記ページで解説していますので、ユーザー管理を行いたいという方は是非読んでみてください。

【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractUser編】 カスタムユーザーをAbstractBaseUserを継承して作成する手順の解説ページアイキャッチ 【Django】カスタムユーザー(独自のユーザー)の作り方【AbstractBaseUser編】

スポンサーリンク

掲示板アプリでモデルを利用してみる

では、ここまで説明してきた内容を踏まえて、実際にモデルの利用例を示していきたいと思います。

この Django 入門に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでフォームを利用してみる で作成したウェブアプリに対してモデルを導入する形で、モデルの利用例を示していきたいと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

より具体的には、今までユーザーを管理するクラスとして User、コメントを管理するクラスとして Commentviews.py に定義して利用してきていましたが、ユーザーを管理するモデルクラス User とコメントを管理するモデルクラス Comment を定義し、views.py に定義したクラスの代わりにこれらのモデルクラスを利用するようにしていきたいと思います。

変更内容の概要

今までは単なるクラスを利用し、さらにリストでインスタンスを管理するようにしていましたが、上記の様にモデルクラスを導入することで、単なるクラスではなくモデルクラスを利用し、さらにデータベースでインスタンス(レコード)を管理するようになります。

モデルクラスの定義

では、まずはモデルクラスとして UserComment を定義していきたいと思います。

前述の通り、既に views.py に UserComment を定義していますが、これは単なるクラスであってデータベースの管理には向いていないので、モデルクラスとして UserComment を定義し、こちらのモデルクラスの方を利用するようにしていきたいと思います。

User の定義

ということで、まずはモデルクラス User を定義しましょう!これも少し前に触れましたが、Django フレームワークには標準でユーザー管理モデル User が用意されています。こちらを利用すればログイン機能なども実現しやすくなるのですが、今回は新たに自身で User を定義し、それを利用するようにしていきたいと思います。

また、User モデルクラスでは、今まで使用していた views.py の User と同等のデータを扱えるようにしたいため、名前 (username) ・メールアドレス (email) ・年齢 (age) の3つのフィールドを持つモデルクラスとして定義を行います。ID (id) に関しては、別途プライマリーキーとなるフィールドを指定しなければ、自動的にプライマリーキーのフィールドとして追加されることになります。

さらに、前述の通り、モデルクラスはアプリフォルダ内の models.py に定義を行います。今回、アプリ名は forum としていますので、forum フォルダ内に存在する models.py にモデルクラスの定義を追記していくことになります。

具体的には、User モデルクラスの定義例は下記のようになります。

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_length32 に設定しています。

また、__str__ メソッドを定義しており、これにより User のインスタンスそのものを出力した場合には、username フィールドに設定されている文字列が出力されることになります。

この __str__ メソッドに関しては下記ページで解説しているため、詳しくは下記ページをご参照いただければと思います。

モデルの__str__メソッドの説明ページアイキャッチ 【Django】モデル(Model)の__str__メソッドとは?

上記を定義し、マイグレーションを実行すれば、idusernameemailage のフィールドを持つテーブルがデータベースに作成されることになります。

Comment の定義

続いては、モデルクラス Comment を定義します。

基本的には User の時と同様で、今まで使用していた views.py の Comment と同等のデータを扱えるようにクラスにフィールドを持たせるようにします。

Comment の場合は本文 (text)・投稿日 (date) をフィールドを持たせれば良いので、下記のように models.py を変更すれば良いことになります。

Commentモデルクラス
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 を指定することで、レコードとしてデータベースに新規作成する際に、自動的にそのタイミングの日時がフィールドの値として設定されることになります。

また、上記を定義し、マイグレーションを実行すれば、idtextdate のフィールドを持つテーブルがデータベースに作成されることになります。

モデルクラスの利用(views.py

続いては、前述で説明した通り、views.py から UserComment の定義を削除し、これらの代わりに先ほど定義したモデルクラス UserComment を利用するように処理を変更していきます。

views.py で定義していた UserComment を利用しているのは views.py で定義した関数とテンプレートファイルになります。

まずは、views.py を変更していきたいと思います。また、最初に結論を述べておくと、テンプレートファイルの方は変更不要になります。理由は後ほど説明します。

views.py の変更必要箇所

まず、views.py では UserComment を定義していましたが、これらは不要になるので削除を行います。その代わり、models.py で定義された UserComment を利用するために、models.py から UserCommentimport を行う必要があります。

そして、これらのクラスを利用している箇所を、モデルクラスの UserComment を利用するように変更していきます。といっても、元々利用していたのはコンストラクタくらいで、モデルクラスでもコンストラクタは利用可能であるため、その部分に関する変更はほぼ不要になります。ただし、UserComment の両方において、id に関してはテーブルへの新規追加時に自動的に採番されるため、 コンストラクタへの id の指定は不要になります。

また、今までリストでインスタンスを管理していたのですが、ここからはインスタンス(レコード)はデータベースのテーブルで管理されるようになります。そのため、コメントの投稿やユーザーの登録によって新規に作成されたインスタンスの保存は、リストに対してではなくデータベースのテーブルに対して行う必要があります。これは、インスタンスから save メソッドを実行させることで実現できます。

また、インスタンスを取得する際にはリストからではなくデータベースのテーブルから取得する必要があります。これには、マネージャーを利用してモデルクラスを操作する で紹介したマネージャーを利用します。

変更後の views.py

上記のポイントを踏まえて変更を行なった 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 との差分を確認していただくのが一番分かりやすいと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

ポイントだけ、ここで簡単に説明していきます。

モデルクラスの import

まず、ポイントの1つ目がモデルクラスの import になります。先ほど示した views.py では下記部分で models.py から UserCommentimport を行なっています。この from . から始まる import は Django でよく使用する形式のものになりますので、是非覚えておいてください。

モデルクラスのimport
from .models import User, Comment

レコードの取得

ポイントの2つ目がデータベースからのレコードの取得になります。

users_viewcomments_view においては、それぞれ User に対応するテーブルと Comment に対応するテーブルから全レコードの取得を行なっています。これを行なっているのが下記部分になります。

全レコードの取得(User)
users = User.objects.all()
全レコードの取得(Comment)
comments = Comment.objects.all()

objects は マネージャーを利用してモデルクラスを操作する で紹介したマネージャーであり、このマネージャーからテーブルへの操作を行うことができます。今回は all メソッドを利用しているので、各テーブルの全レコードのクエリーセットが返却値として得られることになります。前述の通り、filter メソッドを利用すれば検索条件を指定してクエリーセットを取得することも可能です。

また、user_viewcomment_view においては、それぞれ User に対応するテーブルと Comment に対応するテーブルから特定の id を持つ1つのレコードの取得を行なっています。

特定のレコードの取得(User)
user = get_object_or_404(User, id=user_id)
特定のレコードの取得(Comment)
comment = get_object_or_404(Comment, id=comment_id)

ここで利用している get_object_or_404 は Django フレームワークに用意されたショートカット関数の1つで、第1引数に指定したモデルクラス、つまりテーブルから、キーワード引数で指定する条件に当てはまるレコードを取得する関数となります。

レコードを取得するだけであればマネージャーを利用して objects.get を実行することでも実現可能ですが、get_object_or_404 の場合は単にレコードを取得するだけでなく、条件に当てはまるレコードが存在しない場合は例外を自動的に発生してくれます。

例えば、元々の views.pycomments_view では下記のような処理を行なっており、comment_idid に持つインスタンスがリストに存在するかどうかを判断し、存在しない場合は例外を発生させ、存在する場合のみインスタンスを取得するような処理を実装していました。これと同様の処理を1つの関数で実現するのが get_object_or_404 となります(get_object_or_404 の場合は、インスタンスの取得先はリストではなくデータベースになります)。

元々のcomments_view
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_viewpost_view では User のテーブルに対するレコードの新規作成および Comment のテーブルに対するレコードの新規作成をそれぞれ行なっています。

今回モデルクラスを利用するようになりましたが、モデルクラスの使用前後で基本的な処理の流れは変わりません。フォームから送信されてきたデータを受け取り、そのデータに基づいてインスタンスを生成し、そのインスタンスの保存を行なっています。ただし、インスタンスの保存先はデータベースのテーブルとなりますので、インスタンスに save メソッドを実行させて保存を行うようにしています。

また、モデルクラスを利用する前は単にリストにインスタンスを追加することで保存を行なっていましたが、今回からはデータベースのテーブルにレコードとして保存が行われるようになりますので、ウェブアプリを再起動したような場合でもレコードはデータベースのテーブルに残り続けることになります。

スポンサーリンク

モデルクラスの利用(テンプレートファイル)

上記のように views.py を変更することで、コンテキストにセットされるデータも自然と変化することになります。具体的には、単なるクラスのインスタンスがモデルクラスのインスタンスになり、単なるクラスのインスタンスのリストがクエリーセットに変化することになります。

なので、テンプレートファイルから参照される変数も変化することになるのですが、今回の場合はテンプレートファイルの変更は不要となります。

テンプレートファイルからは、参照するインスタンスのデータ属性にアクセスしていますが、今回 models.py に定義した UserComment は、元々 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に対応するモデル

User のインスタンスを生成し、そのインスタンスに save メソッドを実行させた際には、この forum_user のテーブルにレコードが保存されることになります。

2つ目のテーブルが forum_comment で、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のフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

ですが、今回からモデルを導入し、データベースにデータを保存するようになったため、ウェブアプリを終了しても追加したレコードの情報は残り続けることになります。逆に言えば、レコードを削除する際にはデータベースの操作を行なってレコードを削除する手順が必要になったことになります。

今回は、レコードの削除の例は示しませんが、レコードの削除は例えばインスタンスに delete メソッドを実行させることで実現可能です。例えば、コメントやユーザーの一覧にリンクやボタンを追加し、それらがクリックされた際に対応するコメントやユーザーを削除するような動作を実現してみても面白いと思います。

コメント一覧ページに削除ボタンを追加する様子

レコードの削除の例だけでなく、いろんな拡張方法が考えられますので、是非ご自身でウェブアプリの拡張にも挑戦してみていただければと思います!

まとめ

このページでは、Django のモデルについて解説しました!

モデルはデータベースの管理を行う仕組みであり、モデルクラスを models.py に定義して利用することになります。

また、モデルクラス自体はデータベースのテーブルに対応しており、モデルクラスのインスタンスがテーブルのレコードに対応しています。

データベースの管理と聞くと地味なイメージを持たれるかもしれませんが、ウェブアプリにおいて扱うデータはウェブアプリの特徴を決める重要な要素であり、それを決定するのがモデルとなります。そのため、モデルクラスの定義によってウェブアプリの仕上がりが決まるといっても過言ではありません。自身が開発したいウェブアプリで扱う必要のあるデータを考え、それに基づいて適切にモデルクラスを定義することが重要です。

また、モデルクラスをデータベースに反映するためにはマイグレーションが必要となります。これについても是非頭に入れておいてください!

さて、今回モデルについて説明を行いましたが、モデルを使いこなす上で重要になるのが リレーションを利用してモデルクラス同士を関連付ける でも少し触れたリレーションになります。これにより、独立していたモデルクラス同士に関連性を持たせることができ、そしてそれによって様々な機能を実現することができるようになります。下記ページの次回の連載では、このリレーションについて解説していきたいと思います!

Django のリレーションについての解説ページアイキャッチ 【Django入門7】リレーションの基本

同じカテゴリのページ一覧を表示