Django モデル入門(models.py)

本稿ではモデルについて詳しく見ていきたいと思います。まずはDBのカラムの型を一通り網羅し、各カラムの属性についても触れていきます。複合インデックスやリレーション、それからモデルの継承などを取り扱いたいと思います。途中長くなるようなら複数回に分割します。

はじめに

本稿ではViewについて取り扱わないようにします。というのは、その場合にはviews.pyはもちろんのことテンプレートの話も当然出てきますし、モデルのはなしに注力できなくなるためです。

そこですでに紹介済みの管理サイトの機能を用いてView(画面)について代用します。そのための方法は、以下の「STEP 6 (少し脱線) 管理画面にCompanyモデルを」で触れていますので、この手順を前提にしています。

モデルの構造

簡単にモデルの構造について触れたいと思います。

まず真っ先に注目すべきは、1行目のmodles.Modelです。Pythonではクラスの宣言において()中に継承するクラスを指定します。つまりmodles.Modelの子ということです。

このモデルから生成されるテーブルのカラムはこのクラスのフィールドが元になります。この例ではuserやauth_tokenなどがそれです。

その他メソッドもありますが、モデルにはあらかじめ用意されているメソッドのほか、自由に追加も出来ます。まぁ「クラスを継承している」という話をちょっと別の表現しただけですね…

モデルのフィールド

まずはモデルのフィールドから見ていきます。

フィールドは先ほども説明したとおり、テーブルのカラムに対応しています。以下のようにフィールドを並べていき、makemigrationsにmigrateを経てDBにはクラス名をテーブル名に、フィールド名をカラム名にしたテーブルが出来上がります。

name = models.CharField()

ここでは、models.XxxField()のXxxの部分とその引数について紹介していきます。とはいっても全部を紹介するのは公式ドキュメントに譲って、 str, int, date, datetimeあたりを中心に扱っていきます。

CharField()

name = models.CharField('法人名', max_length=512, null=False, blank=False)

strを扱う場合に使用します。DB上のカラム型としてはVARCHARです。

第1引数の文字列ですが、verboseフィールド名(詳細な名前)ということで積極的に使うことも出来ますし、特に登場させずにプログラムすることも出来ます。変数名(メンバー名)の説明をした名詞を充てておくと良いと思います。指定しない場合、つまりデフォルト値としてメンバー名のアンダースコアをブランクに変換したものが使われます。

第2引数のmax_lengthですが、こちらはVARCHARに指定されるLengthです。そしてDjangoのバリデーションでも使われます。

第3引数のnullですがデフォルトはFalseです。DB上ではいわゆるNOT NULL制約です。DB上で何が起こるかはすぐに想像がつくと思いますが、未指定の場合にどちらがデフォルトなのかを覚えれてられる人は構いませんが、そうでない場合は必ずTrueかFalseを指定するようにしましょう。

最後に第4引数のblankですが、これもデフォルトはFalseです。nullがDBの仕様と紐付いているのに対して、blankはバリデーション由来、つまりプログラム側の話です。つまりそのようにPythonプログラムが振る舞うって話であってDBとは無関係です。

なので、Djangoアプリから空を入れようと思っても弾かれますが、DBのツールを使ったりMySQLのクライアントから直接SQLを実行したりすれば空文字でも何の問題もなくinsert可能です。

ちょっと混乱してきたかも知れませんが、nullとblankでTrueとFalseがあるということは、全部で以下の4パターンですね。(太字はデフォルトです)

#1null=False, blank=FalseDB上はNULLは許容されませんし、画面上も空は許されません。
#2null=True, blank=FalseDB上はNULLが許容されて、画面上は空は許容されません。
#3null=False, blank=TrueDB上はNULL不可ですが、画面上は空を許容します。
#4null=True, blank=TrueDB上はNULLが許容され、画面上も空が許されます。

#3って何のこと?ってこれにだけ引っかかりを感じた方は鋭いです。これが許されるのはCharFieldやTextFieldで、これらは値としてNULLだけでなく””つまり空文字があり得ます。そのためstr系の型に関しては、取り得る組み合わせになっています。

結局どうしたら?って方は、まずNULLでしか表せないのか、それらが例えば0や””(空文字)で代用できないのかが分かれ道かと思います。NULLを許容すると、例えばcity.replace("市", "")なんてコードを書いたときにNoneにreplaceなんてありませんとエラーになるのでcityがNoneでないことを確認した上で実行する必要が生じます。

もちろん常にNoneに対する操作をしていないかを意識しながらコードを書くことは大切ですし、そのケースのテストをすることも大切ですが、それらをすり抜けたときのためになるべく安全に倒しておくというのも品質を上げる方法の一つかと思います。

null=Falseが決まれば、あとは画面等での使われ方を、名前が空欄でも申し込める?電話番号が無いと連絡不能、年齢はどっちでも良いか、のように考えていけば良いと思います。

先ほどの例で、0をNULLの代用にしようと述べましたが、年齢だと0歳が対象に含まれていないならいいのですが、0歳を想定するならー1や999などあり得ない数値を代わりにする必要が生じ、NULLを嫌がったために、他のロジックで特別なことをする可能性も生まれていることは理解しておくべきです。

#2はデータの初期値がある場合、例えばプログラムで新規レコード作ってから画面表示してユーザーに新規登録(だけど実は変更)させるが、名前は必須なので画面上は空のままで保存出来ない等といった場合には採用するかも知れません。(使い道の一例を挙げているだけです)

TextField()

summary_value = models.TextField('サマリー値', default="")

扱うのはstrですね。多量のテキストを扱う場合のフィールドです。このフィールドのデフォルトのフォームウィジェットは Textarea です。max_length 属性を指定した場合、自動生成されたフォームフィールドの Textarea ウィジェット内で有効ですが、DBには反映されません。

IntegerField()

change_count = models.IntegerField('変更回数', null=True, blank=False)

intを扱う場合に使用します。もちろんDB上のカラム型としてもINTです。

DateField()

update_date = models.DateField('更新年月日', blank=True, null=True)

datetime.date インスタンスによって表される日付のフィールドです。日付のみで良いときに使います。

DateTimeField()

last_access = models.DateTimeField()

 datetime.datetime インスタンスによって表される日付と時刻のフィールドです。是非とも覚えておくべきなのは次の使い方です。

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

モデル設計にありがちな、作成日時と更新日時のカラムを設けるケースで、それらを自動的に行わせる定義です。auto_now_addをTrueにすることで作成日時が自動的に入ります。auto_nowをTrueにすることで、自動的に更新時の日時が入るようになります。

他にもバリデータとのからみで、たくさんのフィールドが用意されています。わかりやすい例ではEmailFieldやURLFieldは値はもちろんstrなのですが、それぞれの形式に従っているかどうかのチェックが提供されます。その全量については、こちらをご参照下さい。

ForeignKey()

user = models.ForeignKey(User, on_delete=models.CASCADE)

他のテーブルとのリレーションにあたる構成です。ここではDjangoデフォルトで作成されるUserモデルとリレーションを設けています。

フィールドオプション

これまで登場するたびに触れてきましたが、そういった物もふくめフィールドオプションとしてまとめます。

オプションデフォルト値説明
nullFalseTrue の場合、データベース内に NULL として空の値を保持することを許します。Falseの場合NOT NULL制約が課されているということです。
blankFalseTrue の場合、フィールドはブランクになることが許容されます。このオプションはバリデーション由来であり、DBの設定値とは関連がありません。
defaultN/Aフィールドのデフォルト値を設定するために使用します。intの場合に0とかstrの場合に””というのがありがちですが、デフォルト値をNULLにしたいときはNoneを指定することになります。
primary_keyFalseTrue の場合、設定したフィールドはそのモデルの主キーとなります。そしてどこにもこの指定が無ければ、idカラムを自動的に作るという動作をします。
uniqFalseTrue の場合、そのフィールドはテーブル上で一意となる制約を受けます。
db_indexFalseTrueの場合、そのフィールド(カラム)に対してindexが生成されます。
verbose右欄参照ForeignKey、ManyToManyField 、OneToOneField の 3 つを除いた各フィールドは、任意の第 1 引数をとしてverboseをとります。verboseとは詳細な名前で、デフォルト値はフィールドの属性名のアンダースコアをスペースに変換したものです。

モデルのメソッド

続いてメソッドを見ていきます。(もちろん全部ではありません)

__str__()メソッド

モデルを表す文字列を返すようにします。モデルを文字列表現する必要に迫られると使われるので、必要最低限でありながらオブジェクトを特定できる情報を含めると助かる局面があると思います。

まず本稿で真っ先に遭遇するのは管理サイトでの表示です。

モデルの構造のところで例示したAuthTokenでは以下のように表示されます。

以下のように__str__()が存在しない場合はどうなるでしょう。

以下のように番号で呼ばれちゃって残念な感じですね。

save()メソッド

もう一つ是非とも触れておいた方がいいのがsave()です。オブジェクトを保存するたびに何らかの処理を必ずやりたいといったことは時折あるとおもいます。そんなときはsave()をオーバーライドしてしまえば良いです。

def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)
        do_something_else()

その場合に、おそらくは元々のsave()の動作もさせたいのだと思いますので、super().save()の呼び出しを忘れずに行いましょう。

to_dict()メソッド

最後は個人的に必ず追加しているメソッドです。

from django.forms.models import model_to_dict
・・・
    def to_dict(self):
        return model_to_dict(self)

model_to_dict()を実行するだけのメソッドですがこれを追加しています。オブジェクトを辞書形式でダンプするのにとても便利です。

Meta オプション

モデルのクラス内側に class Meta というクラスを定義することで、そのモデルのメタデータを設定することができます。なんて言われても何のこと?って思われるかも知れませんね。

例えばカラムfirst_nameに対するインデックスを定義したいときはそのクラスのフィールドfirst_nameの定義として以下のようにすれば良かったですね。

first_name = CharField(db_index=True)

では、first_nameとfamily_nameの組み合わせでインデックスを生成したいときはどうでしょうか。どうように組み合わせでのユニーク制約はどうでしょうか。カラムに対する設定と言うよりはテーブルに対する設定という色合いが出てくると思いますが、それを担当するのがMetaオプションです。インデックスやユニーク制約の他に、デフォルトの並び順、通常モデル名から自動生成されるテーブル名の指定など、まさにテーブルレベルの設定を担います。

インデックス

1つ目だけフルに掲載して、残りは該当箇所のみを掲載していきます。

class Student(models.Model):
    first_name = CharField('名', max_length=32, null=False, blank=False)
    last_name = CharField('姓', max_length=32, null=False, blank=False)
    birthday = DateField('誕生日')

    class Meta:
        indexes = [
            models.Index(fields=['last_name', 'first_name']),
            models.Index(fields=['first_name'], name='first_name_idx'),
        ]

複合インデックスの作成のためにご紹介しましたが、ご覧の通り1つでも作成可能です。またインデックス名を指定することも可能です。

なお、以下の指定でも作成は可能ですが、将来非推奨になるとのことです。(将来削除される機能なので現在非推奨というのがよく見かけるアナウンスなのですが、もう一段手前の表現です。しかもそれがmay be ということで、どないやんねんって表現ですが、止めておいた方が無難ですね)

        index_together = [
            ["last_name", "first_name"],
        ]

制約

インデックスと同じですね。

constraints = [
    models.UniqueConstraint(
        fields=['corporate_number', 'process', 'change_date'], 
        name='unique_record'),
]

こちらも以下でも作成できますが、may be deprecated in the future.です。

unique_together = [['driver', 'restaurant']]

順序

ordering = ['-birthday']

テーブル名

class Meta:
db_table = 'auth_token'

まとめ

以上、モデル作成に必要そうなものをひとまずまとめてみました。他にも必要なものが出てくるとは思いますが、適宜知識の幅を広げていければ良いと思います。

シリーズの案内

 親記事 Hello Django
 前の記事 Django 静的ファイル
 次の記事 Django マイグレーション入門