我编写了一些API,它们各自的功能在事务块内执行。我在一个/多个模型的实例上调用
save()
方法(经过一些修改),并在 Elasticsearch 中连续索引实例的一些 JSON 相关信息。我希望数据库回滚,即使由于某种原因某个实例的 save()
或对 Elasticsearch 的索引失败。
现在,问题出现了,即使在交易块内,
post_save()
信号也会被调用,这是一个问题,因为这些信号正在触发一些通知。
有没有办法只有在交易成功完成后才触发
post_save()
信号?
我认为最简单的方法是使用transaction.on_commit()。下面是一个使用 models.Model 子类
Photo
的示例,它只会在当前事务结束后与 Elasticsearch 通信:
from django.db import transaction
from django.db.models.signals import post_save
@receiver(post_save, sender=Photo)
def save_photo(**kwargs):
transaction.on_commit(lambda: talk_to_elasticsearch(kwargs['instance']))
请注意,如果
transaction.on_commit()
在不在活动事务中时执行,它将立即运行。
其实不然。这些信号与数据库事务的成功或失败无关,但与 save 方法本身有关 - 在调用之前触发 pre_save 信号,在调用之后触发 post_save 信号。
这里有两种方法:
instance.saved_successfully = True
,您将在 post_save 处理程序中测试该标志。有道理吗?
附注
如果您严格需要绑定到事务提交信号,请查看此包:https://django-transaction-hooks.readthedocs.org/en/latest/;看起来该功能已集成在 Django 1.9a 中。
我遇到了严重的问题,Django 的管理员不允许在修改内联子对象时在父对象上进行 post_save 事务。
这是我对抱怨在原子块中间进行查询的错误的解决方案:
def on_user_post_save_impl(user):
do_something_to_the_user(user)
def on_user_post_save(sender, instance, **kwargs):
if not transaction.get_connection().in_atomic_block:
on_user_post_save_impl(instance)
else:
transaction.on_commit(lambda: on_user_post_save_impl(instance))
编辑:在 Django 中引入 on_commit 后,此答案不再相关。
我们正在使用这个小金块:
def atomic_post_save(sender, instance, **kwargs):
if hasattr(instance, "atomic_post_save") and transaction.get_connection().in_atomic_block:
transaction.on_commit(lambda: instance.atomic_post_save(sender, instance=instance, **kwargs))
post_save.connect(atomic_post_save)
然后我们只需在我们喜欢的任何模型上定义一个
atomic_post_save
方法:
class MyModel(Model):
def atomic_post_save(self, sender, created, **kwargs):
talk_to_elasticsearch(self)
有两点需要注意:
atomic_post_save
。atomic_post_save
。