Djangoのsignals.pre_deleteとpost_delete
やりたいこと
あるモデルのレコードが削除されたとき、なんからの処理を行いたい
やりかた
Djangoの場合はsignalという仕組みが用意されています
例えばこのようにモデルとsignalsの設定をします
from django.db import models from django.db.models.signals import pre_delete, post_delete class Author(models.Model): name = models.CharField(max_length=100) class Entry(models.Model): title = models.CharField(max_length=100) body = models.CharField(max_length=1000) author = models.ForeignKey(Author) def pre_author_delete(sender, instance, **kwargs): print "pre_author_delete : %s" % instance.name def post_author_delete(sender, instance, **kwargs): print "post_author_delete : %s" % instance.name def pre_entry_delete(sender, instance, **kwargs): print "pre_entry_delete: : %s" % instance.title def post_entry_delete(sender, instance, **kwargs): print "post_entry_delete : %s" % instance.title
コンソールからAuthorを1つと関連するEntryを3つ作成します
>>> from app.models import * >>> author = Author.objects.create(name='me') >>> Entry.objects.create(author=author, title='title1', body='body1') Out[6]: <Entry: Entry object> >>> Entry.objects.create(author=author, title='title2', body='body2') Out[7]: <Entry: Entry object> >>> Entry.objects.create(author=author, title='title3', body='body3') Out[8]: <Entry: Entry object>
Entryをひとつ削除してみます。
>>> Entry.objects.get(title='title3').delete() pre_entry_delete: : title3 post_entry_delete : title3
pre_entry_deleteとpost_entry_deleteが呼ばれるのがわかります
Authorを削除してみます。
>>> Author.objects.get(name='me').delete() pre_entry_delete: : title1 pre_entry_delete: : title2 pre_author_delete : me post_entry_delete : title2 post_entry_delete : title1 post_author_delete : me
関連モデルである、Entryもすべて削除されるので、Authorに対して1回、残っているEntry2つに対しても1回ずつpre_deleteとpost_deleteが呼ばれます
pre_deleteや、post_deleteでエラーが発生した場合はどうなるか
やってみます
pre_deleteの処理をちょっと変更して、データを登録し直します。
def pre_author_delete(sender, instance, **kwargs): if True: raise Exception('error') print "pre_author_delete : %s" % instance.name def post_author_delete(sender, instance, **kwargs): print "post_author_delete : %s" % instance.name def pre_entry_delete(sender, instance, **kwargs): print "pre_entry_delete: : %s" % instance.title def post_entry_delete(sender, instance, **kwargs): print "post_entry_delete : %s" % instance.title
Authorを削除してみます。
In [5]: author.delete() --------------------------------------------------------------------------- Exception Traceback (most recent call last) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>() ----> 1 author.delete() /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/base.pyc in delete(self, using) 574 collector = Collector(using=using) 575 collector.collect([self]) --> 576 collector.delete() 577 578 delete.alters_data = True /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in decorated(self, *args, **kwargs) 59 forced_managed = False 60 try: ---> 61 func(self, *args, **kwargs) 62 if forced_managed: 63 transaction.commit(using=self.using) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in delete(self) 237 if not model._meta.auto_created: 238 signals.pre_delete.send( --> 239 sender=model, instance=obj, using=self.using 240 ) 241 /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/dispatch/dispatcher.pyc in send(self, sender, **named) 170 171 for receiver in self._live_receivers(_make_id(sender)): --> 172 response = receiver(signal=self, sender=sender, **named) 173 responses.append((receiver, response)) 174 return responses /Users/yuhei/Dropbox/workspace/django_signals/app/models.py in pre_author_delete(sender, instance, **kwargs) 15 def pre_author_delete(sender, instance, **kwargs): 16 if True: ---> 17 raise Exception('error') 18 print "pre_author_delete : %s" % instance.name 19 Exception: error In [6]: Author.objects.all() Out[6]: [<Author: Author object>]
もちろんエラーが発生します。そしてデータの削除は実行されません。
こんどはpost_deleteの処理でエラーが発生するようにしてみます
def pre_author_delete(sender, instance, **kwargs): print "pre_author_delete : %s" % instance.name def post_author_delete(sender, instance, **kwargs): if True: raise Exception('error') print "post_author_delete : %s" % instance.name def pre_entry_delete(sender, instance, **kwargs): print "pre_entry_delete: : %s" % instance.title def post_entry_delete(sender, instance, **kwargs): print "post_entry_delete : %s" % instance.title
In [3]: author.delete() pre_author_delete : me --------------------------------------------------------------------------- Exception Traceback (most recent call last) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>() ----> 1 author.delete() /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/base.pyc in delete(self, using) 574 collector = Collector(using=using) 575 collector.collect([self]) --> 576 collector.delete() 577 578 delete.alters_data = True /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in decorated(self, *args, **kwargs) 59 forced_managed = False 60 try: ---> 61 func(self, *args, **kwargs) 62 if forced_managed: 63 transaction.commit(using=self.using) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in delete(self) 267 if not model._meta.auto_created: 268 signals.post_delete.send( --> 269 sender=model, instance=obj, using=self.using 270 ) 271 /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/dispatch/dispatcher.pyc in send(self, sender, **named) 170 171 for receiver in self._live_receivers(_make_id(sender)): --> 172 response = receiver(signal=self, sender=sender, **named) 173 responses.append((receiver, response)) 174 return responses /Users/yuhei/Dropbox/workspace/django_signals/app/models.py in post_author_delete(sender, instance, **kwargs) 18 def post_author_delete(sender, instance, **kwargs): 19 if True: ---> 20 raise Exception('error') 21 print "post_author_delete : %s" % instance.name 22 Exception: error In [4]: Author.objects.all() Out[4]: []
もちろんエラーが発生しますが、Author.objects.all()はモデルを返さなくなるので、削除自体は完了してるように見えます。
ところがDBにはレコードが残ってます。
shellを立ち上げ直すとAuthor.objects.all()でしっかり値が返ってきます。
これはどういう状況なんでしょうか?
ちょっと調べてもよくわからなかったので宿題にします。
とりあえず動きとしては「post_deleteの処理でエラーが発生した場合、削除処理はロールバックされる」ようです。
では関連オブジェクトの削除でpre_deleteがエラーになった場合はAuthorは削除されるでしょうか?
def pre_author_delete(sender, instance, **kwargs): print "pre_author_delete : %s" % instance.name def post_author_delete(sender, instance, **kwargs): print "post_author_delete : %s" % instance.name def pre_entry_delete(sender, instance, **kwargs): if True: raise Exception('error') print "pre_entry_delete: : %s" % instance.title def post_entry_delete(sender, instance, **kwargs): print "post_entry_delete : %s" % instance.title
In [4]: author.delete() --------------------------------------------------------------------------- Exception Traceback (most recent call last) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>() ----> 1 author.delete() /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/base.pyc in delete(self, using) 574 collector = Collector(using=using) 575 collector.collect([self]) --> 576 collector.delete() 577 578 delete.alters_data = True /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in decorated(self, *args, **kwargs) 59 forced_managed = False 60 try: ---> 61 func(self, *args, **kwargs) 62 if forced_managed: 63 transaction.commit(using=self.using) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in delete(self) 237 if not model._meta.auto_created: 238 signals.pre_delete.send( --> 239 sender=model, instance=obj, using=self.using 240 ) 241 /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/dispatch/dispatcher.pyc in send(self, sender, **named) 170 171 for receiver in self._live_receivers(_make_id(sender)): --> 172 response = receiver(signal=self, sender=sender, **named) 173 responses.append((receiver, response)) 174 return responses /Users/yuhei/Dropbox/workspace/django_signals/app/models.pyc in pre_entry_delete(sender, instance, **kwargs) 21 def pre_entry_delete(sender, instance, **kwargs): 22 if True: ---> 23 raise Exception('error') 24 print "pre_entry_delete: : %s" % instance.title 25 Exception: error In [5]: Author.objects.all() Out[5]: [<Author: Author object>] In [6]: Entry.objects.all() Out[6]: [<Entry: Entry object>, <Entry: Entry object>]
この場合、データはまったく消えてません。関連オブジェクトの削除にひもづけられたpre_deleteがエラーになった場合、元のdeleteのレシーバオブジェクトの削除は実行されません
post_deleteでも同様でしょうか?
def pre_author_delete(sender, instance, **kwargs): print "pre_author_delete : %s" % instance.name def post_author_delete(sender, instance, **kwargs): print "post_author_delete : %s" % instance.name def pre_entry_delete(sender, instance, **kwargs): print "pre_entry_delete: : %s" % instance.title def post_entry_delete(sender, instance, **kwargs): if True: raise Exception('error') print "post_entry_delete : %s" % instance.title
In [3]: author.delete() pre_entry_delete: : title1 pre_entry_delete: : title2 pre_author_delete : yuhei --------------------------------------------------------------------------- Exception Traceback (most recent call last) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>() ----> 1 author.delete() /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/base.pyc in delete(self, using) 574 collector = Collector(using=using) 575 collector.collect([self]) --> 576 collector.delete() 577 578 delete.alters_data = True /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in decorated(self, *args, **kwargs) 59 forced_managed = False 60 try: ---> 61 func(self, *args, **kwargs) 62 if forced_managed: 63 transaction.commit(using=self.using) /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/db/models/deletion.pyc in delete(self) 267 if not model._meta.auto_created: 268 signals.post_delete.send( --> 269 sender=model, instance=obj, using=self.using 270 ) 271 /Users/yuhei/.virtualenvs/django14/lib/python2.7/site-packages/django/dispatch/dispatcher.pyc in send(self, sender, **named) 170 171 for receiver in self._live_receivers(_make_id(sender)): --> 172 response = receiver(signal=self, sender=sender, **named) 173 responses.append((receiver, response)) 174 return responses /Users/yuhei/Dropbox/workspace/django_signals/app/models.py in post_entry_delete(sender, instance, **kwargs) 24 def post_entry_delete(sender, instance, **kwargs): 25 if True: ---> 26 raise Exception('error') 27 print "post_entry_delete : %s" % instance.title 28 Exception: error In [4]: Author.objects.all() Out[4]: [] In [5]: Entry.objects.all() Out[5]: []
Author.objects.all()やEntry.objects.all()は値を返さなくなります。しかし、例によってdbには値が残ってて、shellを立ち上げ直すと値が取得できます。