目次
Contents
Model
複合キーは主キーにできない
リレーションまとめ
リレーション |
使うフィールド |
使い方 |
---|---|---|
一対一 |
OneToOneField |
どちらか一方のモデルに OneToOneField をくっつける |
多対一 |
ForeignKey |
多側に ForeignKey をくっつける |
多対多 |
ManyToManyField |
どちらか一方のモデルに ManyToManyField をくっつける |
Model の実装例
from django.db import models class Publisher(models.Model): """出版社モデル""" class Meta: db_table = 'publisher' name = models.CharField(verbose_name='出版社名', max_length=255) def __str__(self): return self.name class Author(models.Model): """著者モデル""" class Meta: db_table = 'author' name = models.CharField(verbose_name='著者名', max_length=255) def __str__(self): return self.name class Book(models.Model): """本モデル""" class Meta: """ 対応するテーブルや、複数カラムに対するインデックスやユニーク制約などの モデル全体に対する付加情報を記述する """ # テーブル名を定義 # 定義しないと、 `アプリケーション名_モデルのクラス名をスネークケースにした文字列` がテーブル名になる db_table = 'book' title = models.CharField( verbose_name='タイトル', # フィールド名 max_length=255, # 文字列の最大文字数 # choices: フォーム利用時にセレクトボックスに表示する選択肢 # validators: 文字種チェックなどのバリデーションを指定 error_messages={'invalid': 'title ちがうよー'} # バリデーションNGの場合のエラーメッセージ ) publisher = models.ForeignKey( # 多対一のリレーション: 多側に ForeignKey をくっつける Publisher, verbose_name='出版社', # ForeignKey と OneToOneField には on_delete をつける癖をつけよう on_delete=models.PROTECT, # 自身のレコードは削除されない ) authors = models.ManyToManyField( # 多対多のリレーション: 一方のモデルに ManyToManyField をくっつける # * マイグレーション実行すると Django が自動的に中間テーブルを作成してくれる Author, verbose_name='著者' ) price = models.IntegerField( verbose_name='価格', null=True, # NULL制約 unique=False, # ユニーク制約 blank=True, # フォーム利用時に入力必須にするかどうか db_index=False, # DB のインデックスを設定するかどうか default=0, # レコード登録時に値が指定されなかったときのデフォルト値 ) description = models.TextField(verbose_name='概要', null=True, blank=True) publisher_date = models.DateField(verbose_name='出版日') def __str__(self): # 管理サイトに表示されるよ return self.title class BookStock(models.Model): """本の在庫モデル""" book = models.OneToOneField( # 一対一のリレーション: どちらか一方のテーブルに OneToOneField をくっつける Book, verbose_name='本', on_delete=models.CASCADE # 自身のレコードも削除される ) quantity = models.IntegerField(verbose_name='在庫数', default=0)
on_delete
6種類くらいあって、用途に応じて選べる
Django 2.0 から、必須の引数となる
それ以前のバージョンでは、デフォルトで
CASCADE
UserManager
https://github.com/django/django/blob/master/django/contrib/auth/models.py#L131
-
こうすると登録できる
objects = MyUserManager()
TODO: これは Model のところじゃなくて、認証のところかもしれない。いったんここに置いておく。
Django ORM
単体のオブジェクトを保存・更新するような行レベルのクエリ操作: モデルオブジェクトのメソッドを利用する
-
データベースのテーブルレベルのクエリ操作: モデルの「モデルマネージャー」 (
objects
) を経由してクエリセットAPI のメソッドを利用するモデルマネージャーは通常、モデルクラスに
objects
という名前で保存されている
1件だけ取得する
User.objects.get() # この `objects` がモデルマネージャー
モデルが返る
1件も見つからないと
DoesNotExist
2件以上見つかると
MultipleObjectsReturn
全件取得する
User.objects.all()
即座にデータベースにはアクセスせず、クエリセットオブジェクトを返す
しかるべきタイミングでデータベースアクセスする = 遅延評価
1件も見つからなくても例外発生しない、空のリストを返す
検索条件をつけて取得する
User.objects.filter(is_active=True)
即座にデータベースにはアクセスせず、クエリセットオブジェクトを返す
filter()
を何度も繋げて書ける
filter をつなげる例
keyword = request.GET.get('keyword') queryset = Book.objects.filter() if keyword: queryset = queryset.filter(title=keyword) # ここでクエリが発行される (print すると発行される) print(queryset)
この例の場合、発行されるクエリの総数は1本
OR 条件
from django.db.models import Q Book.objects.filter(Q(title='Django Book') | Q(price=1000))
Q
と|
(パイプ) を使う
>, <, >=, <=
Book.objects.filter(price__gt=1000) # >1000 Book.objects.filter(price__lt=1000) # <1000 Book.objects.filter(price__gte=1000) # >=1000 Book.objects.filter(price__lte=1000) # <=1000
__
(アンダーバー2つ) でフィールド名とキーワード (gt
,lt
,gte
,lte
) をつなぐ
IN
Book.objects.filter(price__in=[900, 1000]) # IN(900, 1000)
__
(アンダーバー2つ) でフィールド名とキーワード (in
) をつなぐIN 句の中身はリストで書く
LIKE
Book.objects.filter(title__icontains='Django') # ILIKE '%Django%' Book.objects.filter(title__contains='Django') # LIKE '%Django%'
__
(アンダーバー2つ) でフィールド名とキーワード (icontains
,contains
) をつなぐ大文字と小文字を区別しない:
icontains
大文字と小文字を区別する:
contains
EXISTS
exists: レコードが存在するか否か True/False で返す
Book.objects.all().exists() Book.objects.filter(title__icontains='Django').exists()
ORDER BY
order_by:
# 降順はフィールド名の前に ``-`` つける Book.objects.all().order_by('-price') # 複数フィールドでソートするときはカンマ区切り Book.objects.all().order_by('price', 'publish_date')
リレーション先のモデルを使った条件検索
Book.objects.filter(publisher__name='自費出版社')
OneToOneField
,ForeignKey
,ManyToManyField
でリレーションしていると、リレーションつけたフィールド名__リレーション先モデルのフィールド名
で JOIN できる
モデルクラス名を全部小文字にしたのに _set
がつく
_set
というのは子テーブルのデータを参照する django の機能モデルクラス名を全部小文字にしたのに
_set
がつく
values() と values_list()
values()
辞書のクエリセットで取得できる。
-
https://docs.djangoproject.com/ja/2.1/ref/models/querysets/#values
>>> Blog.objects.filter(name__startswith='Beatles').values() <QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
values_list()
タプルのリストのクエリセットで取得できる。
-
https://docs.djangoproject.com/ja/2.1/ref/models/querysets/#values-list
>>> Entry.objects.values_list('id', 'headline') <QuerySet [(1, 'First entry'), ...]>
遅延評価
クエリセットを返す all()
や filter()
がクエリを発行するタイミング (データベースアクセスするタイミング) はこれら
for
ループなどイテレーションが開始されたタイミング-
[]
を使ってスライスしたタイミング[0:5]
のように範囲指定するとクエリは即時発行されない
オブジェクトを直列化したタイミング
オブジェクトを
REPL
やprint()
で表示したタイミングlen()
でサイズを取得したタイミングlist()
で強制的にリストに変換したタイミングbool()
で強制的にBoolean
に変換したタイミング
トランザクション
select_for_update()
-
select_for_update
はtransaction.atomic
ブロック内で使いましょう自動コミットモードでクエリセットを評価すると、行ロックされないため
TransactionManagementError
が発生します
select_for_update
のタイムアウトエラー (他のトランザクションにロックがとられていて待ち時間内にロックが取得できなかった) はOperationalError
が発生しますが、キャッチするのはその親のDatabaseError
にしましょうMySQL の場合、
nowait
およびskip_locked
引数は MySQL 8.0.1+ でのみサポートされています。
User モデルを拡張したい
現場で使える Django の教科書《基礎編》 P.63 参照のこと