"関係マネージャ(related manager)"とは、1対多または多対多の関係するコンテキストで使用されるマネージャのことです。これは2つのケースで起こります:
ForeignKey リレーションの "反対側"。つまり:
from django.db import models
class Blog(models.Model):
# ...
pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, null=True)
上記の例では、以下のメソッドがマネージャ blog.entry_set で利用可能です。
ManyToManyField リレーションの両側:
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
この例では、以下のメソッドは topping.pizza_set と pizza.toppings の両方で利用できます。
非同期バージョン: aadd()
リレーション先オブジェクトのセットに、指定したモデルオブジェクトを追加します。
例:
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.
上の例では、 ForeignKey リレーションの場合、 QuerySet.update() を使って更新を行います。これにはオブジェクトが既に保存されている必要があります。
引数 bulk=False を使用することで、関係マネージャが e.save() を呼び出して更新を行うことができます。
しかし、多対多のリレーションシップで add() を使用すると、 save() メソッドは呼び出しません (bulk 引数は存在しません)。 QuerySet.bulk_create() を使用してリレーションシップを作成します。リレーションシップの作成時にカスタムロジックを実行する必要がある場合は、 m2m_changed シグナルを待ち受けます。
すでに存在するリレーションに add() を使っても、リレーションは複製されませんが、シグナルは発生します。
多対多のリレーションシップの場合、 add() は *objs 引数としてモデルインスタンスかフィールドの値(通常はプライマリキー)を受け付けます。
必要であれば、引数 through_defaults を使用して、新しい 中間モデル インスタンスの値を指定します。呼び出し可能オブジェクトを through_defaults 辞書の値として使用することができ、中間インスタンスを作成する前に一度だけ評価されます。
非同期バージョン: acreate()
新しいオブジェクトを作成して保存し、リレーション先オブジェクトのセットに入れます。新しく作成されたオブジェクトを返します:
>>> b = Blog.objects.get(id=1)
>>> e = b.entry_set.create(
... headline="Hello", body_text="Hi", pub_date=datetime.date(2005, 1, 1)
... )
# No need to call e.save() at this point -- it's already been saved.
これは下記のコードと等価です(しかし、よりシンプルです):
>>> b = Blog.objects.get(id=1)
>>> e = Entry(blog=b, headline="Hello", body_text="Hi", pub_date=datetime.date(2005, 1, 1))
>>> e.save(force_insert=True)
リレーションシップを定義するモデルのキーワード引数を指定する必要はないことに注意してください。上の例では、 create() にパラメータ blog を渡していません。Django は新しい Entry オブジェクトの blog フィールドを b に設定すべきであると判断します。
必要であれば、 through_defaults 引数を使用して、新しい 中間モデル インスタンスの値を指定します。呼び出し可能オブジェクトを through_defaults 辞書の値として使用できます。
非同期バージョン: aremove()
リレーション先オブジェクトのセットから指定されたモデルオブジェクトを削除します:
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.remove(e) # Disassociates Entry e from Blog b.
上記の例では add() と同様に e.save() が呼び出されて更新が行われます。しかし、多対多のリレーションシップで remove() を使用すると、 QuerySet.delete() を使用してリレーションシップを削除するので、モデルの save() メソッドは呼び出されません。リレーションシップが削除されたときにカスタムコードを実行したい場合は、 m2m_changed シグナルを待ち受けます。
多対多のリレーションシップの場合、 remove() は *objs 引数としてモデルインスタンスかフィールドの値(通常はプライマリキー)を受け付けます。
ForeignKey オブジェクトでは、このメソッドは null=True の場合のみ存在します。リレーション先フィールドを None (NULL) に設定できない場合、リレーション先オブジェクトを別のオブジェクトに追加せずに削除することはできません。上の例では、 b.entry_set() から e を削除することは e.blog = None と等価であり、 blog ForeignKey には null=True がないので、これは無効です。
ForeignKey オブジェクトの場合、このメソッドは bulk 引数を受け取り、操作の実行方法を制御します。もし True (デフォルト) なら、 QuerySet.update() が使用されます。もし bulk=False なら、代わりに個々のモデルインスタンスの save() メソッドが呼び出されます。これはパフォーマンスを犠牲にし、 pre_save シグナルと post_save シグナルをトリガーします。
多対多のリレーションシップの場合、キーワード引数 bulk は存在しません。
非同期バージョン: aclear()
リレーション先オブジェクトのセットからすべてのオブジェクトを削除します:
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.clear()
これはリレーション先のオブジェクトを削除するのではなく、単に関連付けを解除するだけであることに注意してください。
remove() と同様に、 clear() は ForeignKey で null=True の場合にのみ使用可能で、 bulk キーワード引数も受け付けます。
多対多のリレーションシップの場合、キーワード引数 bulk は存在しません。
非同期バージョン: aset()
リレーション先のオブジェクトのセットを置き換えます:
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)
このメソッドには clear 引数を渡すことができます。もし False (デフォルト) なら、 remove() を使って新しいセットから欠けている要素を削除し、新しい要素だけを追加します。もし clear=True なら、代わりに clear() メソッドが呼び出され、セット全体が一度に追加されます。
ForeignKey オブジェクトの場合、 bulk 引数は add() と remove() に渡されます。
多対多のリレーションシップの場合、キーワード引数 bulk は存在しません。
set() は複合操作であるため、競合状態になりやすいことに注意してください。例えば、 clear() を呼び出してから add() を呼び出すまでの間に、新しいオブジェクトがデータベースに追加される可能性があります。
多対多のリレーションシップの場合、 set() は objs 引数としてモデルインスタンスかフィールド値(通常はプライマリキー)のリストを受け付けます。
必要であれば、引数 through_defaults を使用して、新しい 中間モデル インスタンスの値を指定します。呼び出し可能オブジェクトを through_defaults 辞書の値として使用することができ、中間インスタンスを作成する前に一度だけ評価されます。
注釈
add(), aadd(), create(), acreate(), remove(), aremove(), clear(), aclear(), set(), そして aset() はすべて、すべてのタイプのリレーション先のフィールドに対してデータベースの変更を即座に適用することに注意してください。つまり、リレーションシップのどちらの側でも save()/asave() を呼び出す必要はありません。
prefetch_related() を使用すると、 add(), aadd(), remove(), aremove(), clear(), aclear(), set(), aset() メソッドがプリフェッチキャッシュをクリアします。