Djangoで削除時に外部キー制約によって例外が発生する問題の対処法
はじめに
Django で汎用 View django.views.generic.DeleteView を用いた削除時に外部キー制約によって ProtectedError の例外が発生するのをキャッチする方法を紹介します。
PROTECT されている場合に削除しようとする場合の挙動
Django では、モデルに外部キーを設定できます。モデルの第二引数では、参照先が削除等された場合の挙動が設定できます。
class Comment(models.Model):
user = models.ForeignKey(User, models.PROTECT)
ここで、参照先を削除できなくする models.PROTECT という設定ができます。この設定をした場合、参照先は削除できなくなります。
そして、公式ドキュメントにはこのような記述があります。
Prevent deletion of the referenced object by raising ProtectedError, a subclass of django.db.IntegrityError.
モデルフィールドリファレンス | Django ドキュメント | Django
https://docs.djangoproject.com/ja/3.0/ref/models/fields/#arguments
削除を試みると、 ProtectedError 例外が発生するという仕様です。例外ということは、誤って削除が試みられると 500 エラーとなってしまいます。
汎用 View でシンプルに実装したい
一方で、せっかく Django で実装するのですから、汎用 View でリレーション確認などの記述無しでシンプルに削除したいですよね。そこで下記のように構築したとします。
class UserDeleteView(DeleteView):
model = User
success_url = reverse_lazy('user-list')
ここで先程示したように他のモデルから PROTECT されていた場合、UserDeleteView は ProtectedError 例外エラー(500 エラー)を返してしまいます。
例外をキャッチする
View では例外をキャッチするのみの実装とします。
設計思想 | Django ドキュメント | Django
https://docs.djangoproject.com/ja/3.0/misc/design-philosophies/
ほとんど汎用 View 使う理由がないような感じはしますが、post 関数を上書きします。
class UserDeleteView(DeleteView):
model = User
success_url = reverse_lazy('user-list')
def post(self, request, *args, **kwargs):
try:
obj = self.get_object()
obj.delete()
except models.ProtectedError as e:
messages.error(request, f'「{obj}」は紐付けられているため削除できません。')
return redirect('user-list')
これで、ProtectedError が発生したら削除されず、フラッシュメッセージが表示されます。