revision-up-to: | 8961 (1.0) |
---|
データモデル を作成したら、次はデータベースからデー タを取り出す必要があります。このドキュメントでは、モデルから利用できるデー タベース抽象化 API と、オブジェクトを生成、取得、更新する方法について説明し ます。モデルの照合オプションの詳細は データモデルリファレンス を参照してください。
このリファレンスでは、以下のような Poll アプリケーションを参考に話を進めま す:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.headline
Django では、データベーステーブル上のデータを Python オブジェクトで表現する ために、モデルクラスがデータベーステーブルを表現し、クラスのインスタンスが テーブル上のレコードを表現するという直感的なシステムを使っています。
オブジェクトを生成するには、キーワード引数を使ってモデルクラスのインスタン スを生成し、 save() メソッドを呼び出してデータベースに保存します。
モデルクラスは Python パス上のどこからでも import でき、期待通りに動作しま す (わざわざこのような説明をするのは、以前のバージョンの Django ではモデル の import 方法がかなり風変わりだったからです)。
モデルが mysite/blog/models.py というファイルで定義されているとすると、 オブジェクトの作成は以下の例のようになります:
>>> from mysite.blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
この操作によって、背後では INSERT SQL 文が実行されます。 Django はユー ザが明示的に save() を呼び出すまでデータベースを操作しません。
save() メソッドには戻り値がありません。
See also
save() には高度な使い方のためのオプションがありますが、ここでは解説 しません。詳しくは save() のドキュメントを参照してください。
ワンステップでオブジェクトを生成して保存するには create メソッドを
すでにデータベース上にあるオブジェクトへの変更を保存するには save() を 使います。
Blog インスタンス b5 がすでにデータベース上にあるとすると、以下の例 は b5 の名前を変更して、データベース上のレコードを更新します:
>> b5.name = 'New name' >> b5.save()
この例では、背後で UPDATE SQL 文が実行されています。 Django は明示的に save() を呼び出すまでデータベースを操作しません。
ForeignKey フィールドの更新は、通常のフィールドへの変更と同じです。すな わち、適切な型のオブジェクトを代入して保存すると、フィールドの値を更新でき ます:
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
ManyToManyField の更新は少し違います。リレーションにレコードを一つ追加 したい場合は add() メソッドを使います:
>> joe = Author.objects.create(name="Joe") >> entry.authors.add(joe)
間違った型のオブジェクトを外部キーに代入したり add() したりすると Django はエラーを出します。
オブジェクトをデータベースから取得するには、モデルクラスのマネジャ (Manager) を介してクエリセット (QuerySet) を構築します。
クエリセットはデータベース上にあるオブジェクトの集まりを表現しています。 クエリセットには、集合を指定パラメタに従って絞り込むための条件である フィルタ (filter) を複数個持たせられます。 SQL 用語でいえば、クエリセット は SELECT 文であり、フィルタは WHERE や LIMIT のような限定節に あたります。
クエリセットはモデルのマネジャから取得します。モデルには最低一つの マネジャがあり、デフォルトでは objects という名前がついています。 マネジャにはモデルクラスから直接アクセスしてください:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
Note
(「テーブルレベル」の操作と「レコードレベル」の操作を分離させるため、マ ネジャはモデルのインスタンスではなくモデルクラスだけからアクセスできる ようになっています。)
モデル内でのクエリセットの主なソースはマネジャです。マネジャは、データベー スオブジェクト上の全てのオブジェクトを表す「ルートの」クエリセットであるか のように振舞います。例えば、初期クエリセットである Blog.objects には、 データベース上の全ての Blog オブジェクトが入っています。
テーブルからオブジェクトを取得する最も単純な方法は全てのオブジェクトを取得 するというものです。全オブジェクトを取得するには、マネジャの all() メソッ ドを使って下さい:
>>> all_entries = Entry.objects.all()
all() メソッドはデータベース上の全てのオブジェクトを表現するクエリセッ トを返します。
(Entry.objects がクエリセットを返すというのなら、なぜ単に Entry.objects と書かないのでしょうか?それは、ルートのクエリセットであ る Entry.objects が特別扱いされていて、値評価できないようになっているか らです。 all() メソッドは、値評価 できる クエリセットを返します。
マネジャによって提供される、いわゆるルートクエリセットを使えば、データベー ステーブル上の全てのオブジェクトを表せます。とはいえ、通常は全オブジェクト の集合からサブセットだけを取り出したいことでしょう。
サブセットを作成するには、フィルタ条件を追加して、初期クエリセットをリファ インする必要があります。クエリセットの洗練には、主に二つの方法があります:
照合パラメタ (上の関数定義における **kwargs) は、後述の フィールドの照合 で解説するフォーマットにせねばなりません。
例えば、 2006 年のブログエントリを表すクエリセットを取得するには、以下のよ うに filter() を使います:
Entry.objects.filter(pub_date__year=2006)
(Entry.objects.all().filter(...) のように、 all() を使わなくてもよ いことに注意して下さい。 all() を使っても問題なく動作しますが、 all() が必要となるのはルートクエリセットから全てのオブジェクトを取り出 したい場合だけです。)
クエリセットをリファインした結果は、それ自体クエリセットになります。従って、 リファイン操作は連鎖させられます。例えば:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.now()
... ).filter(
... pub_date__gte=datetime(2005, 1, 1)
... )
この例では、データベースの全てのエントリを表す初期クエリセットに対し、 filter() をかけた後に exclude() を実行し、さらにもう一つ filter() をかけています。最終的に得られるのは、 "What" で始まるヘッドラ インのうち、 January 1, 2005 から今日までの間に公開されたエントリです。
クエリセットのリファインを行うと、その都度新たなクエリセットを得ます。新た なクエリセットは以前のクエリセットになんら縛られていません。リファイン操作 のたびに、別個の独立したクエリセットが作成され、個別に保存したり、再利用し たりできます。
例えば:
>> q1 = Entry.objects.filter(headline__startswith="What") >> q2 = q1.exclude(pub_date__gte=datetime.now()) >> q3 = q1.filter(pub_date__gte=datetime.now())
これら 3 つのクエリセットは別個のものです。最初はヘッドラインが "What" で始 まる全てのエントリの入ったベースのクエリセットです。二つ目のクエリセットは、 最初のクエリセットのサブセットであり、 pub_date の値が現在時刻よりも大 きいものを排除します。三つ目のクエリセットも最初のクエリセットのサブセット で、 pub_date の値が現在時刻よりも大きいものだけを選択するようになって います。こうしたリファイン操作は、初期クエリセット (q1) に影響を及ぼし ません。
クエリセットの評価は遅延型 (lazy) です。すなわち、クエリセットの作成自体は データベース操作を引き起こしません。クエリセットは 評価される までデータ ベースへのクエリを実行しないので、延々フィルタを重ねられます:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q
この例では、データベースに 3 度アクセスするように見えますが、実際には一度だ け、最後の行のとき (print q) しかアクセスしません。一般に、 QuerySet の結果は、その内容を「調べる」まで、データベースから取り出され ません。 QuerySet は、クエリの内容を調べたときにはじめて 値評価 され るのです。値評価がいつ行われているかは、 クエリセットはいつ評価されるのか で詳しく説明しています。
ほとんどの場合、データベース上のオブジェクトを照合するには、 all(), filter(), exclude() メソッドで事足ります。とはいえ、クエリセットが サポートしているメソッドはもっとたくさんあります。クエリセットメソッドの詳 細な説明は、 クエリセット API リファレンス を参照して ください。
クエリセットの返す結果を特定の個数に制限したい場合には、 Python の配列スラ イス表記を使います。これは SQL の LIMIT 節や OFFSET 節と等価です。
以下の例は、最初の 5 オブジェクトだけを返します (LIMIT 5 に相当します):
>>> Entry.objects.all()[:5]
以下の例は、 6 番目から 10 番目までのオブジェクトを返します (OFFSET 5 LIMIT 5 に相当します):
>>> Entry.objects.all()[5:10]
一般に、クエリセットをスライスしてもクエリセットを新たに生成して返すだけで、 クエリの評価は行いません。例外はスライス表記に「ステップ (step)」パラメタを 使ったときです。以下の例では、クエリを実際に実行し、最初の 10 オブジェクト 中から 1 つおきにオブジェクトを取り出したリストを返します:
>>> Entry.objects.all()[:10:2]
リストではなく 単一の オブジェクトを取得したい場合 (SELECT foo FROM bar LIMIT 1 のような場合) には、スライスではなく単純な インデクス指定を行います。以下の例はデータベースのエントリをヘッドラインに ついてアルファベット順に整列した後、最初の Entry を取得して返します:
>>> Entry.objects.order_by('headline')[0]
これはだいたい以下と同じになります:
>>> Entry.objects.order_by('headline')[0:1].get()
ただし、指定条件にマッチするオブジェクトがない場合、前者は IndexError, 後者は DoesNotExist を送出します。詳しくは get() のドキュメントを参 照してください。
フィールドの照合操作によって、 SQL の WHERE 節の中身が決まります。フィー ルドの照合を行うには、 filter(), exclude() および get() といっ たクエリセットのメソッドのキーワード引数を指定します。
基本的に、照合のキーワード引数名は field__lookuptype=value のような形 式をとります (アンダースコアは二重です)。例えば:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
は、(大雑把にいって) 以下のような SQL 文に変換されます:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
How this is possible
Python には、任意の名前と値をもった引数を受け取れる関数を定義する機能が あり、引数名とその値は実行時に評価されます。くわしい情報は公式の Python チュートリアルの キーワード引数 を参照してください。
照合時に無効なキーワード引数が渡されると、 TypeError が送出されます。
データベース API は 2 ダース近くの照合タイプをサポートしています。詳しいリ ファレンスは フィールド照合リファレンス を参照してく ださい。ここでは、よく使う照合タイプをいくつか示します:
「厳密一致」です。例えば:
>>> Entry.objects.get(headline__exact="Man bites dog")
は、以下のような SQL を生成します:
SELECT ... WHERE headline = 'Man bits dog';
照合タイプを指定しなかった場合、つまり二重アンダースコアの入ったキー ワード引数を使わないかぎり、照合条件は exact とみなされます。
例えば、以下の二つの文は同じ意味です:
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
exact はよく使われるため、便宜的にこのような仕様にしています。
大小文字を区別しない一致です。以下のクエリ:
>>> Blog.objects.get(name__iexact="beatles blog")
は、 "Beatles Blog", "beatles blog", "BeAtlES blOG" といったタイト ルの Blog オブジェクトにマッチします。
大小文字を区別する部分一致テストです。以下のクエリ:
Entry.objects.get(headline__contains='Lennon')
は、おおざっぱにいって以下の SQL に変換されます:
SELECT ... WHERE headline LIKE '%Lennon%';
'Today Lennon honored' には一致しますが、 'today lennon honored' には一致しないので注意してください。
大小文字を区別しない icontains もあります。
ここに挙げたのはほんの一部です。全ての照合タイプの解説は フィールド照合タイプのリファレンス を参照してくださ い。
Django では、背後で自動的に SQL JOIN を処理し、照合の際にリレーションを 「追跡」する、強力でありながら直感的な手段を提供しています。リレーションを またぐには、二重アンダースコアを使ってリレーションの張られたフィールドのフィー ルド名を指定します。リレーション間のスパンは、目的のフィールドに到達するま でいくらでも連鎖させられます。
以下の例では、 name が 'Beatles Blog' であるような Blog の Entry エントリオブジェクト全てを取得します:
>>> Entry.objects.filter(blog__name__exact='Beatles Blog')
スパンは好きなだけ深く張れます。
リレーションのスパンは逆方向にも張れます。「逆方向の」リレーションを参照す るには、モデル名を小文字にした名前を使います。
以下の例では、 headline に 'Lennon' を含むような Entry を少なく とも一つ持つような全ての Blog オブジェクトを取得します:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
複数のリレーションをまたいでフィルタを行っていて、中間のモデルにフィルタ条 件に合致する値が存在しないと、 Django はその値が空である (NULL が入って いる) だけの有意なオブジェクトとして扱います。つまり、エラーを送出しません。 例えば、以下のフィルタを考えましょう:
Blog.objects.filter(entry__author__name='Lennon')
(Author モデルは存在するものとします) entry の author を空にし ていると、 Django は author が存在しないために name を辿れませんが、 エラーは送出せず、あたかも name も空であるかのように扱います。たいてい は、この仕様で期待通りに動きますが、 isnull を使うと、少し混乱をきたし ます。すなわち:
Blog.objects.filter(entry__author__name__isnull=True)
は、 author の name が空の Blog だけでなく、 entry の author が空の Blog も返してしまうのです。後者のオブジェクトを返して 欲しくないのなら、以下のように書いてください:
Blog.objetcs.filter(entry__author__isnull=False,
entry__author__name__isnull=True)
ManyToManyField や ForeignKey の逆リレーションを使ってフィルタを行 う場合、 2 種類の異なるフィルタ方法があります。 Blog / Entry のリレー ション (Blog から Entry が 1 対多であるようなリレーション) を考えて みましょう。このようなモデルでは、例えばヘッドラインに 'Lennon' を含み、 かつ 2008 年に公開されたエントリを持つようなブログを検索したい場合があ るでしょう。あるいは、ヘッドラインに 'Lennon' を含むか、 あるいは 2008 年に公開されたエントリを含むようなブログの検索もあり得ます。複数のエン トリが一つの Blog に関連付けられているので、いずれのクエリも実行可能で すし、意味をなす場合があるでしょう。
ManyToManyField を使っている場合にも、同じような状況が起こります。例え ば、 Entry に tags という名前の ManyToManyField があり、 'music' や 'band' というタグに関連付けられたエントリを検索したい場合や、 'music' タグのついた 'public' 状態のエントリを取り出したい場合があるで しょう。
こうした状況をうまく扱うために、 Django には filter() および exclude() といった呼び出しがあります。 filter() に指定した条件は同 時に適用され、条件全てに一致する要素をフィルタします。 filter() を連続 して呼び出すと、通常はオブジェクトの集合により狭い制約をかけますが、多値リ レーションの場合、新たな制約は、前の filter() で絞り込んだリレーション 先をさらに絞り込むのではなく、前の filter() で絞り込んだリレーション先 にリンクしているリレーション元からリレーションを張られている全てのオブジェ クトに対して適用されてしまいます。
この話はちょっとややこしいので、例を挙げて解説しましょう。 'Lennon' をヘッ ドラインに含み、 2008 年に公開されたエントリを含むブログを選択するには、以 下のように書きます:
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
一方、 'Lennon' をヘッドラインに含むか、 あるいは 2008 年に公開された エンリを含むブログを検索するには、以下のように書きます:
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)
後者の例では、最初の filter() でクエリセットに制約をかけ、特定のエント リにリンクを持つブログだけを取り出しています。次の filter() では、取り 出した Blog オブジェクトから、 さらに 二つ目の条件に合うものに制約を かけています。従って、二つ目の filter() で制約をかけている対象の entry は、最初のフィルタで絞り込んだエントリと同じときもあれば、違うと きもあります。一つめの filter() でフィルタしているのはあくまでも Blog であって、 Entry ではないからです。
この挙動は、 exclude() にもあてはまります。一つの exclude() に指定 した条件は、 (多値リレーションの場合は) 各インスタンスに同時に適用されます。 filter() や exclude() を連続して呼び出すと、毎回違うリンク先集合を 使ってフィルタを行ってしまう可能性があるのです。
利便性のために、 Django には pk という照合形式があります。 pk は primary_key を表します。
Blog モデルの例では、主キーは id フィールドなので、以下の二つの文は 等価です:
>>> Blog.objects.get(id__exact=14) # 明示的な形式
>>> Blog.objects.get(id=14) # 暗黙で __exact を表す
>>> Blog.objects.get(pk=14) # pk は暗黙で id__exact を表す
pk は __exact のクエリにしか使えないわけではありません。どのクエリ 用キーワードも pk と組み合わせてかまいません。 pk を使うと、モデル の主キーに対するクエリを表します:
# id が 1, 4 および 7 のブログエントリを取得する >>> Blog.objects.filter(pk__in=[1,4,7]) # id > 14 の全てのブログエントリを取得する >>> Blog.objects.filter(pk__gt=14)
pk による検索は join 越しでも行えます。 例えば、以下の二つの文は等価で す:
>>> Entry.objects.filter(blog__id__exact=3) # 明示的な形式
>>> Entry.objects.filter(blog__id=3) # 暗黙で __exact を表す
>>> Entry.objects.filter(blog__pk=3) # __pk は暗黙で __id__exact を表す
LIKE を使う SQL 文になるようなフィールド照合メソッド (iexact, contains, icontains, startswith, istartswith, endswith, iendswith) では、 LIKE 文で使われる二つの特殊な文字、すなわちパーセ ント記号とアンダースコアを自動的にエスケープします。 (LIKE 文では、パー セント記号は任意の複数文字に対するワイルドカードを表し、アンダースコアは任 意の一文字に対するワイルドカードを表します。)
この機能によって、照合操作を直感的に行え、データベースの抽象化を守れます。 例えば、パーセント記号を含むようなエントリ全てを取得したければ、以下のよう にパーセント記号をそのまま使います:
>>> Entry.objects.filter(headline__contains='%')
Django はクオートの処理に気を配ってくれます。 SQL は以下のような感じになり ます:
SELECT ... WHERE headline LIKE '%\%%';
アンダースコアについても同じようなエスケープを行います。パーセント記号とア ンダースコアはいずれも透過的に処理されます。
データベースへのアクセスを最小限にとどめるため、クエリセット各々にはキャッ シュがあります。効率的なコードを書く上で、キャッシュのからくりを理解してお くのは重要なことです。
クエリセットが新たに生成された時点では、キャッシュは空です。クエリセットを 最初に評価したとき (すなわち、データベースへのクエリが最初に生じたとき)、 Django はクエリ結果をクエリセットオブジェクト内のキャッシュに保存し、明示的 にリクエストした結果だけ (例えば、クエリセットに対してイテレーション操作を する場合には、結果セットの最初の要素) を返します。それ以後は、クエリセット を際利用するとキャッシュ済みの結果を返します。
このキャッシュの挙動をよく覚えておいて下さい。というのも、クエリセットを正 しく扱わないと、おもわぬところで手を噛まれるはめになるからです。例えば、以 下の例では二つのクエリセットを作成し、値を評価して、すぐにクエリセットを捨 ててしまっています:
>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]
そのため、全く同じデータベースクエリを二度実行し、データベースの負荷を倍加 させています。また、 Entry は二つのリクエストを処理する間にも追加された り削除されたりする可能性があるため、二つのリストには必ずしも同じデータベー スレコードが入っているとは限りません。
こうした問題を避けるには、クエリセットを保存して再利用してください:
>>> queryset = Poll.objects.all()
>>> print [p.headline for p in queryset] # クエリセットを評価します。
>>> print [p.pub_date for p in queryset] # キャッシュの値を再利用します。
filter() などで複数のキーワード引数を指定してクエリを行うと、各々のキー ワード引数の表す照合条件は違いに "AND" で結ばれます。より複雑なクエリ (例え ば OR を使ったクエリ) を実行する必要がある場合には Q オブジェクトを 使えます。
Q オブジェクト (django.db.models.Q) は、複数のキーワード引数 をカプセル化するために使われます。キーワード引数は前述の 「フィールドの照合」 で説明したものと同じです。
例えば、以下の Q オブジェクトは単一の LIKE クエリをカプセル化してい ます:
Q(question__startswith='What')
Q オブジェクトは & や | といった演算子で組み合わせられます。二 つの Q オブジェクトを演算子で結ぶと、新たな Q オブジェクトを生成し ます。
例えば、以下の文は二つの question__startswith クエリを "OR" したものを 表す単一の Q オブジェクトを生成します:
Q(question__startswith='Who') | Q(question__startswith='What')
この Q オブジェクトは以下の WHERE 節と等価です:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
Q オブジェクトを & と | で組み合わせれば、好きなだけ複雑なクエ リ文を作成できます。丸括弧を使ったグルーピングも可能です。
キーワード引数をとる照合関数 (filter(), exclude(), get() など) には、複数の Q を固定引数として (名前なしの引数として) 渡せます。複数の Q オブジェクトを照合関数に渡した場合、それらは互いに "AND" で結ばれます。 例えば:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
は、だいたい以下のような SQL になります:
SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
照合関数は Q オブジェクトとキーワード引数を混ぜて使えます。照合関数に渡 した全ての引数は (キーワード引数も Q オブジェクトも) 互いに "AND" で結 ばれます。ただし、 Q を指定する場合はどのキーワード引数よりも前に指定せ ねばなりません。たとえば:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who')
は有効なクエリで、前の例と同じになりますが、以下の文:
# INVALID QUERY
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
は無効です。
See also
OR lookups examples の例には、 Q を使った Django のユニット テストがあります。
二つのモデルオブジェクトを比較するには、標準の Python 比較演算子、すなわち 二重等号符: == を使います。背後では二つのモデルオブジェクト間の主キー値 が比較されます。
上の Entry の例では、以下の二つの文は等価です:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
モデルの主キーが id という名前でなくても問題はありません。どのような名 前であれ、比較は常に主キーを使って行われます。例えば、モデルの主キーのフィー ルド名が name であれば、以下の二つの文は等価です:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
削除用のメソッドには、便宜的に delete() という名前が付いてます。このメ ソッドはオブジェクトをただちに削除し、戻り値を返しません:
e.delete()
複数のオブジェクトの一斉削除も可能です。 QuerySet には delete() メソッドがあり、 QuerySet の全てのメンバを削除します。
例えば、 pub_date が 2005 年の Entry オブジェクトを全て削除するには 以下のようにします:
Entry.objects.filter(pub_date__year=2005).delete()
忘れてならないのは、この処理はできる限り SQL レベルで行われるため、 delete() メソッドが全てのインスタンスの delete() メソッドを呼ぶとは 限らないということです。モデルクラス上で delete() をカスタマイズしてい て、オブジェクトを削除するときに呼び出したいのなら、 QuerySet の delete() メソッドで一括削除するのではなく、直接(QuerySet の各要素を 取り出して逐次 delete() を呼ぶなどして) 「手動で」インスタンスを削除せ ねばなりません。
Django は、オブジェクトを削除する際に、SQLでいう ON DELETE CASCADE 制約 をエミュレートします。すなわち、削除対象のオブジェクトを指すような外部キー を持つ全てのオブジェクトも同時に削除されるのです。:
b = Blog.objects.get(pk=1)
# 次の命令は、 Blog と Blog を指す Entry 全てを削除してしまいます。
b.delete()
delete() は QuerySet のメソッドにすぎず、 Manager 自体には公開 されていないので注意してください。これは誤って Entry.objects.delete() を実行して 全ての エントリを削除してしまわないようにするための安全機構で す。本当に全てのオブジェクトを削除 したい のなら、以下のように明示的に全 てのオブジェクトを表すクエリセットをリクエストしてください:
Entry.objects.all().delete()
クエリセットが表現する全てのオブジェクトのあるフィールドに特定の値を指定し たいような場合があります。 update() メソッドを使えば、この処理を実現で きます。例えば:
# pub_date が 2007 の全エントリのヘッドラインを更新する
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
この操作を行えるのは、リレーションフィールドでないフィールドか、 ForeignKey のフィールドのみです。また、フィールドに指定する値はハードコー ドされた Python の値でなければなりません (つまり、その時の何らかのフィール ド値は直接セットできません)。
ForeignKey のフィールドを更新するには、以下のように、新たなモデルインス タンスを作成して渡します:
>>> b = Blog.objects.get(pk=1)
# 全てのエントリをこの blog に属させる
>>> Entry.objects.all().update(blog=b)
update() メソッドは (delete() と同様)、即座に適用され、値を返しませ ん。また、更新対象のクエリセットには、モデルの主のテーブル一つに対してし かアクセスできないという制限があります。従って、クエリセットを構築するとき に、リレーション先のフィールドでフィルタしたりしてはなりません。複数のテー ブルを操作するようなクエリセットに対しては、 update() は動作しません。
update() メソッドは SQL 文に直接変換されるので注意してください。このメ ソッドは、フィールド値の直接更新を大量一括実行するためのものです。 モデルの save() メソッドを呼出さないので、 (save() の呼び出しに連動 している) pre_save や post_save といったシグナルは発生しません。 クエリセット内の全ての要素に対して save() メソッドを適用したければ、 単に全要素に渡って save() を呼び出してください:
for item in my_queryset:
item.save()
モデル内でリレーション (ForeignKey, OneToOneField, ManyToManyField) を定義すると、そのモデルのインスタンスはリレーション先 のオブジェクトにアクセスするための便利な API を持つようになります。
このドキュメントの冒頭のモデルを例にとると、 Entry オブジェクト e は、 e に関連づけられている Blog オブジェクトに blog という属性 を使って e.blog のようにアクセスできます。
(舞台裏では、この機能は Python の デスクリプタ を使って実装されています。 だからどうだというわけではありませんが、興味のある人のためにここで指摘して おきます。)
Django はまた、リレーションの「相手側」へのアクセス API、すなわちリレーショ ンを張られた側からリレーションを張った側のモデルへのリンクも作成します。例 えば、 Blog オブジェクト b は、リレーションを張った全ての Entry オブジェクトのリストに entry_set 属性を使って b.entry_set.all() の ようにアクセスできます。
この節での例は、全て冒頭に示した Blog Blog, Author, Entry のモデルを使っています。
モデルに ForeignKey フィールドがある場合、そのモデルのインスタンスは、 単に属性を使ってリレーション先 (外部) のオブジェクトを参照できます。
例:
>>> e = Entry.objects.get(id=2)
>>> e.blog # リレーション先の Blog オブジェクトを返します。
外部キー属性の値は取得 (get) も設定 (set) もできます。当然ながら、外部キー への変更は save() を呼び出すまでデータベースに反映されません。例えば:
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
ForeignKey フィールドに null=True が設定されていた場合 (NULL 値 を許している場合)、以下の例のように None を代入できます:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
一対多のリレーションで順方向のアクセスを行うと、その結果はリレーション先の オブジェクトに最初にアクセスした際にキャッシュされます。それ以降のアクセス では、同じオブジェクトインスタンスの外部キーへのアクセスはキャッシュされた 値を返します。例えば:
>>> e = Entry.objects.get(id=2)
>>> print e.blog # データベースを検索して、関連づけられた Blog を返します。
>>> print e.blog # データベースは検索せず、キャッシュを使います。
クエリセットのメソッド select_related() を使うと、一対多のリレーション のリレーション先オブジェクト全てをあらかじめ再帰的にキャッシュに取り込みま す。例えば:
>>> e = Entry.objects.select_related().get(id=2)
>>> print e.blog # Doesn't hit the database; uses cached version.
>>> print e.blog # Doesn't hit the database; uses cached version.
あるモデルが ForeignKey で別のモデルにリレーションを張っている場合、リ レーションを張られた側のモデルのインスタンスは、リレーションを張った側のモ デルの全てのインスタンスを返すマネジャにアクセスでます。リレーションを張っ ている側のモデル名を全て小文字にしたものを FOO とすると、マネジャの名前 のデフォルト値は FOO_set です。このマネジャはクエリセットを返します。ク エリセットには前述の「オブジェクトの取得」の節で説明したフィルタや操作を行 えます。
例を示しましょう:
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Blog に関連づけられた全ての Entry を返します。
# b.entry_set はクエリセットを返すマネジャです。
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
ForeignKey を定義するときに related_name パラメタを設定しておくと、 FOO_set の名前をオーバライドできます。例えば、 Entry モデルの定義を blog = ForeignKey(Blog, related_name='entries') のように改めると、上の コード例は以下のようになります:
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Blog に関連づけられた全ての Entry を返します。
# b.entries はクエリセットを返すマネジャです。
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
ForeignKey 逆参照は、マネジャとしてのアクセスはできず、インスタンスとし てアクセスせねばなりません。例えば:
>>> Blog.entry_set
Traceback:
...
AttributeError: "Manager must be accessed via instance".
前述の「オブジェクトの取得」で説明したクエリセットのメソッドに加えて、 ForeignKey で表現されるマネジャには、リレーション元のオブジェクトの集合 を扱うための追加のメソッドがあります。メソッドの概要は以下に示しますが、詳 細は リレーションのリファレンス を参照してく ださい。
リレーション先セットのメンバを一括で代入するには、イテレーション可能オブジェ クトを代入します。例えば:
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
clear() メソッドを利用できる場合に代入を行うと、まず entry_set から 全てのオブジェクトを除去しておき、その後で右辺の iterable (上の例ではリスト) のオブジェクト追加します。 clear() メソッドを使えない場合は、 entry_set に既に存在するオブジェクトを削除せず、単に右辺の iterable 上 の全てのオブジェクトを追加します。
この節で説明した「逆方向の」操作は、いずれもデータベースを即時変更します。 操作結果は追加、新規作成、削除といった操作を行う度に即座に自動的にデータベー スに保存されます。
多対多のリレーションの場合、リレーションの関係にあるモデルの一方は、互いに もう一方にアクセスするための自動 API を獲得します。この API は一対多のリレー ションにおける「逆方向の」参照のように動作します。
一対多のリレーションとの唯一の違いは、属性の名前づけ規則です。 ManyToManyField を定義した側のモデルはフィールド名をそのまま使いますが、 「反対側の」モデルでは、相手のモデルのモデル名を小文字にして、 '_set' を追加したもの (一対多のリレーションにおける逆方向の参照と同じ) になります。
例を使って説明した方が理解しやすいでしょう:
e = Entry.objects.get(id=3)
e.authors.all() # Entry の全ての Author オブジェクトを返します。
e.authors.count()
e.authors.filter(name__contains='John')
a = Author.objects.get(id=5)
a.entry_set.all() # Author の全ての Entry オブジェクトを返します。
ForeignKey と同様、 ManyToManyField には related_name パラメタ を指定できます。 上の例で、 Entry の ManyToManyField に related_name='entries' を指定していた場合、 Author インスタンスは entry_set ではなく entries という属性を持つようになります。
一対一 (one-to-one) のリレーションは多対多のリレーションと非常によく似てい ます。モデルに OneToOneField を定義すると、モデ ルのインスタンスはリレーション先のオブジェクトを簡単な属性アクセスで参照で きます。
例を示します:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # リレーション先の Entry オブジェクトを返します。
多対多との違いは、逆参照のクエリです。多対多の時と同様、リレーション先のモ デルはリレーション元のモデルに対するマネジャオブジェクトにアクセスできます が、このマネジャはオブジェクトの集合ではなく単一のオブジェクトを表現してい ます:
e = Entry.objects.get(id=2)
e.entrydetail # リレーション先の EntryDetail オブジェクトを返します。
逆リレーション先のオブジェクトが存在しなければ、 Django は DoesNotExist 例外を送出します。
逆参照のリレーションにインスタンスを代入すると、順参照のリレーションと同じ ようにリレーション先を変更できます:
e.entrydetail = ed
他のオブジェクトリレーショナルマッパでは、双方向からリレーションを定義せね ばなりません。 Django の開発者たちはこれを DRY 則 (Don't Repeat Yourself) の侵犯だと考えたため、 Django では片方だけでリレーションを定義すればよいよ うにしています。
しかし、なぜあるモデルクラスが、自分に対してリレーションを張っているモデル クラスのことを、そのクラスがロードされる前に検知できるのでしょうか?
答えは INSTALLED_APPS 設定にあります。最初にモデルをロードした時 に、 Django は INSTALLED_APPS の全てのモデルを走査して、必要に応 じて後方参照をメモリ中に作成します。 INSTALLED_APPS の本質的な機 能の一つは、 Django にモデルドメインの全体像を知らせることなのです。
リレーション先のオブジェクトを照合条件に含むクエリは、通常の値のフィールド の入ったクエリと同じような規則に従います。クエリにマッチ条件として値を指定 する場合、オブジェクトのインスタンス自体か、オブジェクトの主キー値のいずれ かを使えます。
例えば、 id=5 であるようなBlog オブジェクト b に対しては、以下の三 つのクエリはすべて等価です:
Entry.objects.filter(blog=b) # オブジェクトインスタンスを使ったクエリ
Entry.objects.filter(blog=b.id) # インスタンスの id を使ったクエリ
Entry.objects.filter(blog=5) # id を直接使ったクエリ
Django のデータベースマッパで扱うには複雑すぎる SQL 文を書かねばならないよ うな場合には、生の SQL 文実行モードを使えます。
生 raw-SQL 文実行モードの使い方としてお勧めなのは、そのようなクエリ文を実行 するモデルのカスタムメソッドやカスタムマネジャのメソッドを実装するというも のです。 Django はモデルレイヤでデータベースクエリを記述するように 何ら要求してはいません が、このアプローチをとることで、データアクセスのた めのロジックを一箇所にまとめられるので、コード組織化の観点からスマートにな ります。詳しい説明は SQL クエリの直接実行 を参照してください。
最後に、 Django のデータベースレイヤは単にデータベースへの一つのインタフェー スに過ぎないということに注意しておきましょう。データベースには他のツールや プログラム言語、データベースフレームワークを介してアクセスできます。データ ベースについて Django 固有の何かがあるわけではないのです。
Aug 31, 2012