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

削除を試みると、 ProtectedError 例外が発生するという仕様です。例外ということは、誤って削除が試みられると500エラーとなってしまいます。

汎用 View でシンプルに実装したい

一方で、せっかくDjangoで実装するのですから、汎用Viewでリレーション確認などの記述無しでシンプルに削除したいですよね。そこで下記のように構築したとします。

class UserDeleteView(DeleteView):
model = User
success_url = reverse_lazy('user-list')

ここで先程示したように他のモデルからPROTECTされていた場合、UserDeleteViewはProtectedError 例外エラー(500エラー)を返してしまいます。

例外をキャッチする

リレーションを確認するプログラムを実装しません。モデルが確認することなので、Viewでは例外をキャッチするのみの実装とします。

設計思想 | Django ドキュメント | Django

ほとんど汎用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が発生したら削除されず、フラッシュメッセージが表示されます。

コメント