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

@bicstone

大石貴則 (Ōishi Takanori) と申します。 Webエンジニア / セキュリティスペシャリスト / 機械エンジニア です。 プロダクトに幅広く携わり、相互成長し続けられるエンジニアを目指しています。

GitHubLinkedIn