このページでは、Django のリレーションフィールドに指定可能な on_delete
引数について解説します。
下記ページで解説している通り、Django ではモデルクラスにリレーションフィールドを定義することで、2つのモデルクラスのインスタンス同士を関連付けることができるようになります。
【Django入門7】リレーションの基本例えば下記のように Club
と Student
を定義すれば、Student
のインスタンスと Club
のインスタンスの間で多対1の関連付けを行うことが可能となります。そして、これによって、各生徒(Student
のインスタンス)が所属するクラブ(Club
のインスタンス)を管理することができるようになります。
from django.db import models
class Club(models.Model):
name = models.CharField(max_length=32)
class Student(models.Model):
name = models.CharField(max_length=32)
club = models.ForeignKey(Club, on_delete=models.CASCADE, null=True)
このようなモデルクラスの定義を、皆さんも実際に自身で実装したり、ウェブサイトのソースコード等で見かけたりしたことがあるのではないでしょうか?
今回注目したいのが、上記における on_delete
引数になります。この on_delete
引数は、リレーションフィールドの ForeignKey
と OneToOneField
に必須となる引数となります。
この on_delete
引数には、どういう意味があるのでしょうか?
この点について、本ページで解説していきたいと思います!
Contents
on_delete
では、まずは on_delete
について解説していきます。
結論を先に言っておくと、on_delete
は、子オブジェクトを持つ親オブジェクト削除時のアプリの動作を設定する引数になります。
この結論だけだと意味不明と感じる方も多いと思いますので、リレーションについてまず振り返りを行い、その中で子オブジェクトと親オブジェクトの意味合いや、on_delete
の効果について説明していきたいと思います。
リレーション
リレーションとは、2つのインスタンスを関連付けることを言い、Django においてはモデルクラスにリレーションフィールドを定義することで、この関連付けを行うことができるようになります。
リレーションフィールドとは、具体的には下記の3つのことを指しており、リレーションフィールドとしてどれを定義するのかによって、実現可能な関連付けが異なります。
OneToOneField
:1対1の関連付けForeignKey
:多対1の関連付けManyToManyField
:多対多の関連付け
下記は、モデルクラス Comment
にリレーションフィールドを定義する具体例となります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.CASCADE)
この場合、ForeignKey
の第1引数に User
を指定しているため、Comment
のインスタンスと User
のインスタンスとの関連付けを行うことが可能となります。この場合は、Comment
のインスタンスに関連付けられた User
のインスタンスが、そのコメントの投稿者であると考えると関連付けのイメージが湧きやすくなると思います。また、リレーションフィールドが ForeignKey
であるため、この場合は1つの User
のインスタンスに対し、複数の Comment
のインスタンスを関連付けすることが可能となります。
実際のインスタンスの関連付けに関してはビューやモデルクラスのメソッドで実施可能数rことになります。例えば、先ほど示したモデルクラスの定義において、user_x
を User
のインスタンス、comment_1
と comment_2
を Comment
のインスタンスとすれば、下記のように、リレーションフィールドのフィールド名(contributor
)のデータ属性を利用して、これらのインスタンスの関連付けを実施することができます。
comment_1.contributor = user_x
comment_2.contributor = user_x
comment_1.save()
comment_2.save()
関連付け後の、各インスタンスの関係を図で表せば下図のようになります。
今回は、Comment
に対して ForeignKey
を定義しているため、User
のインスタンスとの間で多対1の関連付けが可能となりますが、ManyToManyField
を定義すれば多対多の関連付けが、OneToOneField
を定義すれば1対1の関連付けを行うことができるようになります。
スポンサーリンク
親オブジェクトと子オブジェクト
on_delete
の意味合いが理解しやすくなるよう、次は関連付けに親子関係を導入していきたいと思います。
具体的には、2つの関連付けられたインスタンスに対し、リレーションフィールドが定義されたモデルクラスのインスタンスのことを「子オブジェクト」と記し、それに対してリレーションフィールドの第1引数に指定されたモデルクラスのインスタンスのことを「親オブジェクト」と記して解説を進めていきたいと思います。
例えば前述の下記のように定義したモデルクラスの場合、User
のインスタンスと Comment
のインスタンスが関連付けられているのであれば、User
のインスタンスが「親オブジェクト」となり、Comment
のインスタンスが「子オブジェクト」ということになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.CASCADE)
また、例えば下記のような処理を行った場合、user_x
が親オブジェクトとなり、この user_x
は comment1
と comment_2
の2つの子オブジェクトを持つことになります。
comment_1.contributor = user_x
comment_2.contributor = user_x
comment_1.save()
comment_2.save()
こんな感じで、親オブジェクトと子オブジェクトという位置づけで関連付けについて整理していくと、on_delete
の意味合いが理解しやすくなると思います。
また、以降では、インスタンス同士が関連付けされている状態を示す図では、子オブジェクト側から親オブジェクト側を指す矢印で親子関係を図示するようにしていきたいと思います。例えば、上記の例の場合は次の図のように親子関係が表されることになります。
on_delete
とは
で、ここからが本題の on_delete
についての解説になります。
まず、on_delete
引数は ForeignKey
と OneToOneField
における必須引数となります。これらに対して on_delete
引数を指定しなかった場合は、下記のような例外が発生することになります。
TypeError: ForeignKey.__init__() missing 1 required positional argument: 'on_delete'
そして、この on_delete
とは「子オブジェクトを持つ親オブジェクトが削除される際のアプリの動作を設定する引数」となります。
前述の例であれば、comment_1
と comment_2
の子オブジェクトを持つ「親オブジェクト user_x
」が削除される際のアプリの動作が、ForeignKey
における on_delete
引数によって設定されることになります。
子オブジェクトを持つ親オブジェクト削除時のアプリの適切な動作は開発するアプリによって大きく異なります。そのため、この削除時のアプリの動作を on_delete
引数によって設定できるようになっています。そして、この動作はリレーションフィールド毎に設定することが可能です(ForeignKey
や OneToOneField
毎に設定可能。というか、そもそも必須で設定が必要)。
例えば、前述の例で示した Comment
と User
を「コメント投稿が可能なウェブサイト」で利用することを考えてみましょう。User
のインスタンスはウェブサイトの会員として扱い、Comment
のインスタンスはコメントとして扱っていきます。さらに、Comment
のインスタンスに関連付けられた User
のインスタンスは、そのコメントの投稿者の会員として考えていきます。
この場合、User
のインスタンスを削除するケースとしてはウェブサイト会員の退会が考えられると思います。この会員退会時のアプリの動作を設定するのが on_delete
になります。
例えば、退会する会員が今まで投稿してきたコメントは会員の退会時に一緒に削除してしまいたい場合があると思います。
「コメント投稿が可能なウェブサイト」が日々の近況をつぶやく目的で利用されているのであれば、退会した会員のコメントは削除してしまっても問題ないはずです。
このような場合、on_delete
には CASCADE
を設定しておくのが良いです。これにより、User
のインスタンス削除時に(会員退会時に)、User
インスタンスの持つ子オブジェクト(Comment
のインスタンス)が全て自動的に削除されることになります。
また、ウェブサイトによっては会員退会後も他の会員からコメントが閲覧できるよう、会員退会後もコメントのみは残しておきたい場合もあると思います。
例えば「コメント投稿が可能なウェブサイト」が技術的な議論を行うサイトであった場合、退会した会員のコメントであっても他の会員にとって有益なものも多いはずで、サイトとしてもコメントを残していった方が有益な情報を発信することができ、サイトの価値も向上するはずです。そもそもコメントを消してしまうと議論の流れが分からなくなってしまう可能性も高いです。
このような場合、on_delete
には SET_DEFAULT
(もしくは SET_NULL
or SET
) を設定しておくのが良いです。これにより、User
のインスタンス削除時にも(会員退会時にも)、その User
のインスタンスの持つ子オブジェクト(Comment
のインスタンス)を残すことができます。後述で解説しますが、SET_DEFAULT
を設定しておくと、親オブジェクト削除時に子オブジェクトの新たな親オブジェクトとしてデフォルトオブジェクトが設定されることになります。
このように、開発するアプリの目的や方向性によって子オブジェクトを持つ親オブジェクト削除時の適切な動作は異なります。そのため、開発するアプリの目的や方向性に応じて on_delete
を適切に設定することが重要となります。
逆に、on_delete
が適切に設定されていない場合、親オブジェクト削除時に本来削除すべきでない子オブジェクトが削除されてしまう可能性もあるので注意が必要となります。
あくまでも on_delete
は「子オブジェクトを持つ親オブジェクト」が削除される際のアプリの動作を設定するものであり、「子オブジェクト自体」を削除する際や「子オブジェクトを持たない親オブジェクト」を削除する際の動作には作用しないので注意してください。
また、例えば on_delete
への指定値によっては「子オブジェクトを持つ親オブジェクト」の削除を禁止するようなものがあります。この場合、子オブジェクトを持っている限り親オブジェクトは削除することができません。
ですが、あらかじめ子オブジェクトを削除したり、子オブジェクトとの親子関係を解消したりすれば、その親オブジェクトは「子オブジェクトを持つ親オブジェクト」ではなくなるため、on_delete
の影響がなくなってオブジェクトを削除することが出来るようになります。
ここまで説明してきたように、on_delete
に指定する設定値によって、子オブジェクトを持つ親オブジェクト削除時のウェブアプリの動作が大きく変化しますし、それによって開発されるウェブアプリも大きく異なることになります。
そのため、Django でウェブアプリを開発するのであれば、on_delete
に指定可能な設定値、および、設定値に応じたウェブアプリの動作についてはしっかり理解しておく必要があります。
on_delete
に指定可能な設定値
ということで、まずは on_delete
に指定可能な設定値の一覧を、ここで示しておきたいと思います。
Django 5.1 時点では on_delete
には下記の7つの設定値を指定可能となっています。
CASCADE
:子オブジェクトも一緒に削除するSET_DEFAULT
:子オブジェクトの新たな親オブジェクトとしてデフォルトオブジェクトを設定するSET_NULL
:子オブジェクトの新たな親オブジェクトとしてNULL
を設定するSET
:子オブジェクトの新たな親オブジェクトとして特定の関数の返却値等を設定するDO_NOTHING
:何もしないPROTECT
:子オブジェクトを持つ親オブジェクトの削除を禁止するRESTRICT
:子オブジェクトを持つ親オブジェクトの削除を禁止する(子オブジェクトが先に削除可能なら親オブジェクトも削除する)
一応、各設定値の意味合いを一言で表していますが、これだけだと結局どう動作するかのイメージが湧かない方も多いと思います。
ということで、ここからは上記で挙げた7つの設定値それぞれの違いについて、具体的な例を挙げながら解説をしていきたいと思います。
スポンサーリンク
CASCADE
まずは、CASCADE
について説明していきます。CASCADE
は一番意味合いも分かりやすく、そして使いやすい設定値だと思います。
CASCADE
は親オブジェクト削除時に、その親オブジェクトの持つ子オブジェクトを全て自動的に削除する設定値となります。
例えば、下記のようにモデルクラスを定義した場合、
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.CASCADE)
User
のインスタンスが削除された際に、そのインスタンスが子オブジェクトとして持つ Comment
のインスタンスも全て削除されることになります。
ここまでの例で示してきたような「コメント投稿可能なウェブサイト」の場合、会員(User
)が退会して削除される際に、その会員が今まで投稿してきたコメント(Comment
)も一緒に削除したいような場合に便利な設定値となります。
SET_DEFAULT
CASCADE
が親オブジェクト削除時に「自動的に子オブジェクトを削除する」設定値であるのに対し、ここから説明する SET_DEFAULT
、SET_NULL
、SET
に関しては、親オブジェクト削除時にも「子オブジェクトを残しておく」設定値となります。
まず SET_DEFAULT
は、親オブジェクト削除時に、その親オブジェクトの持つ子オブジェクトの新たな親オブジェクトとして「デフォルトオブジェクト」を設定する設定値となります。
つまり、子オブジェクトを持つ親オブジェクトが削除された際には、それらの子オブジェクトの新たな親オブジェクトとして自動的にデフォルトオブジェクトが設定されることになります。
どのオブジェクトをデフォルトオブジェクトとするかは default
引数の設定によって指定可能です(pk
を指定してデフォルトオブジェクトを設定する)。
on_delete
への SET_DEFAULT
の設定例は下記のようになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.SET_DEFAULT, default=1)
前述の通り、CASCADE
では親オブジェクト削除時に子オブジェクトも一緒に削除されるのに対し、SET_DEFAULT
ではデフォルトオブジェクトの子オブジェクトとして残ることになります。そのため、「コメント投稿可能なウェブサイト」の例で考えれば、会員(User
)が退会して削除された後でも、その会員が今まで投稿してきたコメントを表示し続けたいような場合は便利な設定値になると思います。
この SET_DEFAULT
の意味合いは分かりやすいと思うのですが、使い方は結構難しいのかなぁというのが私の印象です。
まず、on_delete
に SET_DEFAULT
を設定する場合は、上記のように必ず default
引数としてデフォルトオブジェクトの pk
も一緒に指定する必要があります。
上記のようにモデルクラスを定義した場合、Comment
のインスタンスの親オブジェクト(User
)が削除された際に、その Comment
のインスタンスの新たな親オブジェクトとしてはデフォルトオブジェクト、つまり pk
が 1
である User
のインスタンスが設定されることになります。
この新たな親オブジェクトの設定は自動的に行われるのですが、この際に「デフォルトオブジェクトに新たな子オブジェクトを持たせることができない」場合、親オブジェクト削除時に例外が発生することになるので注意してください。
「デフォルトオブジェクトに新たな子オブジェクトを持たせることができない」ケースの1つ目として、デフォルトオブジェクト自体が存在しない場合が挙げられます。上記のモデル定義の例であれば、pk
が 1
である User
のインスタンスが存在しない場合がそれに当たります。
この場合、親オブジェクト削除時には下記のような例外が発生することになります。
django.db.utils.IntegrityError: FOREIGN KEY constraint failed
「デフォルトオブジェクトに新たな子オブジェクトを持たせることができない」ケースの2つ目として、リレーションフィールドが OneToOneField
として定義されている、かつ、デフォルトオブジェクトが既に他の子オブジェクトを持っている場合が挙げられます
この場合、デフォルトオブジェクトに新たな子オブジェクトが追加されることになり、OneToOneField
で実現可能な「1対1の関連付け」の関係性が崩れてしまうことになるため例外が発生します。具体的には、下記のような例外が発生することになります。
UNIQUE constraint failed: on_delete_app_comment.user_id
例えば、下記のようにモデルクラスを定義した場合、User
のインスタンスと Comment
のインスタンスとの間では1対1の関連付けのみが実施可能となります。つまり、1つの親オブジェクトが持つことのできる子オブジェクトは1つのみとなります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.OneToOneField(User, on_delete=models.SET_DEFAULT, default=1)
デフォルトオブジェクトが子オブジェクトを持っていない場合、最初の User
のインスタンスの削除は成功します。ですが、この時点でデフォルトオブジェクトは1つの子オブジェクトを持っていることになります。
ですので、この状態で子オブジェクトを持つ他の User
のインスタンスを削除した場合、その子オブジェクトの新たな親オブジェクトとしてデフォルトオブジェクトが設定されることになりますが、この時に1対1の関係性で無くなってしまうため、例外が発生することになります。
こういった注意点があるため、リレーションフィールドを OneToOneField
で定義する場合は、on_delete
への SET_DEFAULT
の指定は控えた方が良いと思います。
SET_NULL
次に SET_NULL
について説明していきます。
SET_NULL
は、親オブジェクトが削除された際に、子オブジェクトの親オブジェクトを「なし(NULL
)」に設定するための設定値となります。
つまり、on_delete
に SET_NULL
が指定されている子オブジェクトは、親オブジェクトが削除された際に親オブジェクトが存在しない状態になります。子オブジェクトは、親オブジェクトが存在しない状態でデータベースに存在し続けることになります。
on_delete
に SET_NULL
を指定する場合、そのリレーションフィールドには、必ず null=True
の引数指定も必要となるので注意してください。
on_delete
への SET_NULL
の設定例は下記のようになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class History(models.Model):
# 略
customer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
上記のモデルを用いて、「ショッピングウェブアプリ」で顧客(User
)の購入履歴(History
)を管理し、その購入履歴から毎月の売上高の集計や在庫数の管理などを行う例を考えてみましょう(下図において、user_x
と user_y
は User
のインスタンス、history_1
と history_2
は History
のインスタンスとなります)。
この例の場合、顧客(User
)が退会した際にも、その顧客の購入履歴(History
)は残しておく必要があります。購入履歴から売上高の集計や在庫数の管理を行なっているのですから、顧客が退会したからといって購入履歴まで消してしまうと、その分売上高や在庫数が減ってしまって計算が合わなくなってしまいますよね…。
そんな時に、on_delete
に SET_NULL
を設定しておけば、顧客退会時にも購入履歴は残すことができ、正しく売上や在庫数を計算することができるようになります。
こんな感じで、親オブジェクトがいない状態であっても子オブジェクトのみは残しておきたい場合に SET_NULL
は便利だと思います。
スポンサーリンク
SET
続いて SET
について説明していきます。
SET
は、親オブジェクト削除時に、子オブジェクトの新たな親オブジェクトとして SET
の引数で指定した pk
のインスタンスを設定する or SET
の引数で指定した関数の戻り値の pk
のインスタンスを設定する設定値となります。
例えば、下記のようにモデルクラスを定義すれば、User
のインスタンスが削除された際に、その子オブジェクトの新たな親オブジェクトとして pk
が 1
の User
のインスタンスが自動的に設定されるようになります。この場合は、SET_DEFAULT
と同様の動作が実現されることになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.SET(1))
また、SET
には関数を指定することも可能で、例えば下記のようにモデルを定義すれば、親オブジェクト削除時に関数 get_first_user
が実行され、子オブジェクトの新たな親オブジェクトとして get_first_user
の返却値を pk
とするオブジェクトが設定されることになります。
from django.db import models
def get_first_user():
users = User.objects.order_by('name')
return users[0].pk
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.SET(get_first_user))
上記の get_first_user
は、User
の全インスタンスの中から name
がアルファベット順で一番早いインスタンスを親オブジェクトとして設定するような関数にしていますが、もちろん関数の作り方によって異なる User
のインスタンスを親オブジェクトとして設定することも可能です。
また、適当な親オブジェクトが存在しない場合に None
を返却することで、親オブジェクトが存在しない子オブジェクトにすることも可能です。
関数で行う処理によって動的に親オブジェクトを変更したいような場合に SET
は便利な設定値になると思います。ただし、親オブジェクトを付け替えるわけですから、SET_DEFAULT
と同様の注意点に気をつける必要があります(None
を返却する場合は SET_NULL
と同様の注意点に気をつける必要があります)。
DO_NOTHING
on_delete
には DO_NOTHING
を設定することも可能です。
名前の通り、DO_NOTHING
は、親オブジェクト削除時には何も実施しない設定値となります。
つまり、on_delete=DO_NOTHING
が指定されている場合、親オブジェクトが削除されたとしても、子オブジェクトの親は削除された親オブジェクトのままとなります。
ですが、デフォルト設定では、削除されて存在しないインスタンスを親オブジェクトに設定できないようになっているため、on_delete
に DO_NOTHING
が設定されていると、子オブジェクトを持つ親オブジェクトを削除した際に、例外 IntegrityError
が発生することになります。
ただ、これはデフォルト設定の話で、リレーションフィールドに db_constraint=False
を指定すれば、存在しないインスタンスを親オブジェクトに設定できるようになり、上記の例外も発生しないようになります。ですが、それでも子オブジェクトが「存在しないインスタンス」を親オブジェクトとして参照し続けることになるため、そのままアプリを動作させ続けるとデータベース内で不整合が発生する可能性が高くなってしまうので注意してください。
on_delete
への DO_NOTHING
の設定例は下記のようになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Comment(models.Model):
text = models.CharField(max_length=256)
contributor = models.ForeignKey(User, on_delete=models.DO_NOTHING, db_constraint=False)
DO_NOTHING
は、親オブジェクト削除時の動作をアプリに任せるのではなく、開発者やアプリ管理者自らが動作を制御したいような場合に有効な設定値になると思います。ですが、データベース内の不整合を防ぐための実装やデータベースの操作が必要になるため、特に Django やデータベース初心者の方にとっては使用難易度の高い設定値となります。ですので、まずは DO_NOTHING
ではなく他の設定値を利用することを考えるのが良いと思います。
PROTECT
ここまで紹介してきた設定値は全て、親オブジェクト削除時の子オブジェクトの扱い方を決めるためのものでした。
それに対し、ここから紹介する PROTECT
と RESTRICT
は、子オブジェクトを持つ親オブジェクトの削除を禁止するための設定値となります。
まず PROTECT
は、子オブジェクトを持つ親オブジェクトの削除を完全に禁止する設定値となります。子オブジェクトを持つ親オブジェクトを削除しようとした場合、例外 ProtectedError
が発生して削除に失敗します。子オブジェクトを持つ親オブジェクトの削除を防ぎたいような場合に便利な設定値となります。
つまり、PROTECT
が設定されている場合、子オブジェクトを持つ親オブジェクトを削除するためには、その親オブジェクトをまず「子オブジェクトを持たない状態」にする必要があります。そうしなければ、親オブジェクトを削除することができません。
親オブジェクトを「子オブジェクトを持たない状態」にするためには、持っている子オブジェクトを他のオブジェクトの子に設定したり、持っている子オブジェクトを削除したりする必要があります。
on_delete
に PROTECT
を設定する例は下記のようなものになります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class Employee(User):
pass
class Work(models.Model):
contractor = models.OneToOneField(Employee, on_delete=models.PROTECT)
requester = models.OneToOneField(User, on_delete=models.CASCADE)
上記は「仕事の依頼サービス」で利用することを想定したモデルクラスの定義となります。Work
が仕事、Employee
が仕事の請負者、User
が仕事の依頼者としてそれぞれ扱われることを想定した定義となっています。さらに、1つの Work
のインスタンスに対し、1つの Employee
のインスタンスと User
のインスタンスが関連付けられるようになっています。そして、これらが関連付けされた際には、User
のインスタンスと User
のインスタンスの両方が、Work
のインスタンスの親オブジェクトということになります。
この場合、Work
のインスタンスの親オブジェクトである Employee
のインスタンスは、contractor
の OneToOneField
の引数に on_delete=PROTECT
が指定されているため、削除することができないことになります。つまり、仕事の請負人は、依頼者から依頼された仕事が存在する間は退会(削除)できないことになります。
Employee
のインスタンスを削除するためには、そのインスタンスの子オブジェクト(Work
のインスタンス)を事前に削除する必要があります。イメージとしては請け負った仕事を完了させてから、依頼者の User
のインスタンスに Work
のインスタンスを削除してもらう感じになります。
つまり、PROTECT
を設定することで、請負者(Employee
)が請け負った仕事を完了させずに退会して削除されてしまうことを防ぐことができることになります。仕事を請け負ったまま退会されてしまうと、仕事の依頼者は困ってしまいますよね…。
このように、on_delete
に PROTECT
を指定することで、子オブジェクトを持つ親オブジェクトの削除を禁止することができるようになります。上記の例のように、子オブジェクトを持っている状態の親オブジェクトを削除されてしまうとウェブアプリの動作やウェブアプリの運営が破綻してしまうような場合は、それを禁止するために on_delete
に PROTECT
を指定してやるのが良いと思います。
ちなみに、requester
のリレーションフィールドには on_delete=CASCADE
が指定されているため、User
のインスタンスに関しては、子オブジェクトとして Work
のインスタンスを持っていたとしても削除することが可能です。on_delete=CASCADE
が指定されているので、User
のインスタンス削除時には、その子オブジェクトの Work
のインスタンスに削除されることになります。
スポンサーリンク
RESTRICT
最後に RESTRICT
について解説していきます。
RESTRICT
は PROTECT
に似た設定値になりますので、PROTECT
と比較しながら解説を進めていきたいと思います。
また、この章の解説では、下記の Artist
・Album
・Song
の3つのモデルクラスを使って解説を進めていきます。より具体的には、下記の Song
の album
フィールドにおける on_delete
に(つまり 設定値
部分に)、PROTECT
と RESTRICT
のそれぞれを指定したときの動作の違いを説明していきます。
from django.db import models
class Artist(models.Model):
name = models.CharField(max_length=10)
class Album(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
class Song(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=設定値)
ちなみに、上記のモデルクラスの定義は Django の公式サイト の RESTRICT
の解説時に用いられている定義を引用したものになります。Song
は曲、Album
はアルバム、Artist
はアーティストを表現するクラスとなっており、これらを利用して音楽の配信サービスを実現することをイメージしてもらえると、各クラスの関係性がイメージしやすくなると思います。
RESTRICT
と PROTECT
の共通点
前述のとおり、RESTRICT
は PROTECT
に似た設定値となります。例えば、Song
の album
フィールドにおける on_delete
を PROTECT
とし、下の図のようにインスタンスが関連付けられている場合、album_1
の削除を実行すると例外 ProtectedError
が発生して削除に失敗することになります。
ここで、図について補足しておくと、ここからは各リレーションフィールドに指定される on_delete
の設定値が異なる場合があるため、上の図のように、親子関係を表す矢印(矢印の根本側が子、矢側が親)の近くに、その親子関係の関連付けを行うリレーションフィールドの on_delete
への指定値を示すようにしていきます。上図は、song_1
と song_2
が album_1
の子オブジェクトであり、これらを関連付けるリレーションフィールドの on_delete
には PROTECT
が指定されていることを示していることになります。
で、話を戻すと、先ほどと同様のインスタンスの関連付けの構成においては、Song
の album
フィールドにおける on_delete
に RESTRICT
を指定した場合でも、album_1
の削除を実行すると例外が発生して削除に失敗することになります。ただし、on_delete
に RESTRICT
を指定している場合に発生する例外は RestrictedError
なります。
RESTRICT
と PROTECT
の違い
このように、RESTRICT
に関しても、基本的には PROTECT
同様に、子オブジェクトを持つ親オブジェクトとの削除を禁止する設定値と考えて良いです。ですが、異なる設定値である以上、やはり、これらの設定値には違いがあります。
この違いが出るのは、on_delete
に PROTECT
or RESTRICT
が指定されているリレーションフィールドによって関連付けられる親オブジェクトと子オブジェクトの全てが同一の親オブジェクトを持ち、さらに、その同一の親オブジェクトと関連付けられるリレーションフィールドの on_delete
に CASCADE
が指定されている場合になります。以降では、この同一の親オブジェクトのことを「マスターオブジェクト」と呼ばせていただきます(ちょっと、しっくりこないかもしれませんが)。
この場合、マスターオブジェクトを削除すると、親オブジェクトと子オブジェクトとを関連付けるリレーションフィールドの on_delete
に PROTECT
を指定している場合は削除に失敗して例外が発生することになります。それに対し、RESTRICT
を指定している場合は削除に成功することになります。
なぜ、このように削除の結果(成功 or 失敗)が変化するかというと、on_delete
に PROTECT
を指定した場合と RESTRICT
を指定した場合とでは、削除が実行される順序が異なるからになります。
PROTECT
を指定している場合は、削除に失敗する順番で削除が実施されることになり、RESTRICT
を指定している場合は削除に成功できるのであれば削除に成功する順番で削除が実施されることになります。なので、 on_delete
への指定値によって、削除の結果が変化することになります。
この削除の結果が変化する様子を、前述で示した Artist
・Album
・Song
の3つのモデルクラスを使って具体的に確認していきたいと思います。ここでは、先ほど示したインスタンスの関連付けの構成に artist_1
を新規に導入し、さらに artist_1
と album_1
・song_1
・song_2
のそれぞれを関連付ける例で説明を行っていきます。
この構成は、先ほど示した下記条件を満たす関連付けの構成となります。
on_delete
にPROTECT
orRESTRICT
が指定されているリレーションフィールドによって関連付けられる親オブジェクトと子オブジェクトの全てが同一の親オブジェクトを持ち、さらに、その親オブジェクトと関連付けられるリレーションフィールドのon_delete
にCASCADE
が指定されている
この構成において、マスターオブジェクトとなる artist_1
の削除を実行したときの動作を考えてみましょう。
まず、この artist_1
の削除を実行すると、この artist_1
と on_delete
に CASCADE
が指定されたリレーションフィールドで関連付けられたインスタンスの削除が行われることになります。具体的には、album_1
と song_1
と song_2
の削除が行われることになります。ただし、これらは同時に削除されるのではなく、順々に削除が行われていくことになります。で、ここでポイントになるのが、前述の通り、これらの削除を行う順序によって削除の成功 / 失敗が変化するという点になります。今回の例の場合は、album_1
を先に削除するパターンと、song_1
/ song_2
を先に削除するパターンとで削除の結果が変わることになります。
album_1
を先に削除するパターンで考えると、この場合は削除には失敗することになります。なぜなら、album_1
には、on_delete
に PROTECT
or RESTRICT
が指定されたリレーションフィールドで関連付けられた子オブジェクトが存在するからです。
それに対し、song_1
/ song_2
を先に削除するパターンで考えると、この場合は、song_1
と song_2
が先に削除されることになります。song_1
と song_2
には子オブジェクトが存在しないため、on_delete
には無関係に削除に成功することになります。そうなると、album_1
にも子オブジェクトが存在しなくなります。
したがって、album_1
の削除にも成功し、もちろん artist_1
の削除にも成功します。したがって、全インスタンスの削除に成功することになります。
この例のように、on_delete
に PROTECT
を指定した場合も RESTRICT
を指定した場合も「子オブジェクトを持つ親オブジェクトの削除が禁止される」という点は同じになるのですが、削除の順序が異なるため、それによって削除の結果(成功 or 失敗)が変わることがあります。
そして、わざと削除に失敗する順序で削除が実行されるのが、on_delete
に PROTECT
を指定した場合になります。このような順序で削除を実施することで、on_delete
に PROTECT
が指定されているリレーションフィールドによって親オブジェクトと子オブジェクトとが関連付けられている場合、その親オブジェクトの削除には必ず失敗することになります。すなわち、PROTECT
は子オブジェクトを持つ親オブジェクトの削除を完全に禁止する設定値となります。
そのため、PROTECT
の場合は、子オブジェクトを持つ親オブジェクトを削除する場合は、必ず事前に子オブジェクトを削除したり、他の親オブジェクトに関連付け直したりする必要があります。なので、削除の手間はかかります。
それに対し、on_delete
に RESTRICT
が指定されている場合は、極力削除に成功する順序で削除が実施されることになります。そのため、on_delete
に RESTRICT
が指定されているリレーションフィールドによって親オブジェクトと子オブジェクトが関連付けられていたとしても、それらが同一のマスターオブジェクトに関連付けられているのであれば、マスターオブジェクトの削除という1つの操作によって、それらの親オブジェクトと子オブジェクトも同時に削除することが可能となります。
ただし、それらの親オブジェクトと子オブジェクトが同一のマスターオブジェクトに関連付けられていないのであれば、マスターオブジェクトの削除時にも削除に失敗することになります。
例えば、音楽配信サービスで、特定のアーティストとの契約が切れて、そのアーティストが発表したアルバムと曲の配信を停止するような場合、前述の条件を満たすように、そのアーティストと、そのアーティストが発表したアルバム・曲を関連付けておけば、アーティストを削除した際に、そのアーティストが発表したアルバム・曲も一緒に削除することが可能となります。
なんですが、そのアーティストが発表したアルバムではあるものの、そのアルバムに他のアーティストが提供した曲が含まれている場合、その曲は異なる親オブジェクトと関連付けられていることになるため、そのアルバムを発表したアーティストを削除しても削除に失敗することになり、その他のアーティストが提供した曲を含むアルバムの削除を防ぐことができます。
ということで、基本的には RESTRICT
or PROTECT
のどちらを指定したとしても、基本的には子オブジェクトを持つ親オブジェクトの削除は禁止されることになります。ただし、下記の条件を満たす場合のみ、PROTECT
を指定することでマスターオブジェクト削除時の各インスタンスの削除に成功するようになります。
on_delete
にPROTECT
orRESTRICT
が指定されているリレーションフィールドによって関連付けられる親オブジェクトと子オブジェクトの全てが同一の親オブジェクトを持ち、さらに、その親オブジェクトと関連付けられるリレーションフィールドのon_delete
にCASCADE
が指定されている
なんとなくでも PROTECT
と RESTRICT
の違いについては理解していただけたでしょうか?
ハッキリ言って、これらの PROTECT
と RESTRICT
との使い分けはかなり難しいと思います。なので、とりあえず、子オブジェクトを持つ親オブジェクトの削除を防ぎたいのであれば、PROTECT
を指定するので良いと思います。PROTECT
の方が仕組みがシンプルですので、削除に手間がかかろうがコーディングは楽になるのではないかと思います。そして、PROTECT
を指定した状態で関連付けを実施し、その関連付けによって上記の条件を満たすのであれば、その時に初めて RESTRICT
の採用を検討してみるくらいで良いと思います。
ということで、まずは RESTRICT
に関しては、特定の条件を満たさない限りは PROTECT
と同じ作用をもたらす設定値であると考えるくらいで良いと思います。
スポンサーリンク
まとめ
このページでは、Django における on_delete
について解説しました!
on_delete
はリレーションに対して設定を行う引数であり、「子オブジェクトを持つ親オブジェクト削除時のアプリの動作」を設定することのできる引数となります。
この on_delete
には、Django 5.1 時点で下記の7つを設定することが可能です。
CASCADE
:子オブジェクトも一緒に削除するSET_DEFAULT
:子オブジェクトの新たな親オブジェクトとしてデフォルトオブジェクトを設定するSET_NULL
:子オブジェクトの新たな親オブジェクトとしてNULL
を設定するSET
:子オブジェクトの新たな親オブジェクトとして特定の関数の返却値等を設定するDO_NOTHING
:何もしないPROTECT
:子オブジェクトを持つ親オブジェクトの削除を禁止するRESTRICT
:子オブジェクトを持つ親オブジェクトの削除を禁止する(子オブジェクトが先に削除可能なら親オブジェクトも削除する)
これらの設定値の中でどれを使用すべきかは開発したいアプリによって異なります。それぞれの設定値の意味合いをしっかり理解し、開発したいアプリに合わせて on_delete
を適切に設定していくようにしましょう!