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を立ち上げ直すと値が取得できます。