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

ホームプロフィール外部リンクのため、別ウインドウで開きますプライバシーポリシー

© 2023 Oishi Takanori / Made with Gatsby.js