django - Django(v2.0.7) Admin:数据库事务提交后回调
问题描述
我有一个巨大的模型,有很多属性,包括多个 ManyToManyMapping。应用程序中的大部分添加/更新都是通过 REST API 进行的,但为了进行较小的更正,我使用了 Django Admin Form。这个管理表单也有多个内联表单集。
publish_event
在通过表单或 REST API 更新模型后,我想将一些事件发布到 Kafka( )。我希望在事务提交到数据库时发生这种情况,以便监听 Kafka 事件的服务最终不会从数据库中获取陈旧的数据。
我提到了这篇SO 帖子,但它似乎是在每笔交易上都这样做,而不是在每个模型的基础上,并且on_commit 会造成事情被调用两次的问题(更多下文)。
到目前为止我尝试过的事情:
信号:由于添加了 ManyToManyMapping 而被拒绝,
model.save()
需要调用两次,最终发布了 2 个事件。此外,它在模型保存上运行,而不是事务提交,所以在回滚的情况下,我仍然会发布一个事件。覆盖模型的方法:由于与被调用两次
save(self, *args, **kwargs):
相同的原因而被拒绝。model.save()
覆盖 ModelAdmin's
save_model
:这是当我们在表单上点击 Save 时首先要调用的东西之一,所以覆盖它并没有帮助,因为 formset 还没有被处理。因此,包括 M2M 映射在内的完整状态不会在数据库中提交。
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
publish_event()
- 覆盖 ModelAdmin 的
save_related
:起初这似乎是解决方案,但事务尚未提交给 DB。def save_related(self, request, form, formsets, change): form.save_m2m() for formset in formsets: self.save_formset(request, form, formset, change=change) publish_event()
到目前为止,我还没有弄清楚任何回调触发的事务提交后。
解决方案
TLDR:覆盖change_view
深入研究源代码文件django.contrib.admin.option.py
后,似乎保存模型和相关的 M2M 正在由以下代码触发_changeform_view
:
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, not add)
self.save_related(request, form, formsets, not add)
change_message = self.construct_change_message(request, form, formsets, add)
which 被调用changeform_view
which 设置原子事务。这就是我想要覆盖的内容,以便publish_event
一旦将事情提交给 DB 就可以执行:
@csrf_protect_m
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._changeform_view(request, object_id, form_url, extra_context)
该代码依次由change_view
and调用add_view
。
def change_view(self, request, object_id, form_url='', extra_context=None):
return self.changeform_view(request, object_id, form_url, extra_context)
由于我只通过表单进行更新(而不是创建),因此我覆盖change_view
了显式调用publish_event
:
def change_view(self, request, object_id, form_url='', extra_context=None):
change_resp = super(MySampleModelAdmin, self).change_view(request, object_id, form_url, extra_context)
if request.method != 'GET': # since GET also call this and we don't want event published on GET
publish_event()
return change_resp
一旦change_resp = super(MySampleModelAdmin, self).change_view(request, object_id, form_url, extra_context)
执行完成,事务就被提交,所以publish_event
在这一步调用是安全的。在此之后change_view
只是期望得到回应。
编辑:试过on_commit,这似乎也有效。这是基于信号的。
from django.db import transaction
@receiver(post_save, sender='app.MySampleModel')
def send_model_save_event(sender, instance=None, created=False, **kwargs):
if instance is None:
log.info('Instance is Null')
return
transaction.on_commit(lambda: handle_model_after_save(instance.id))
推荐阅读
- eclipse - Cucumber Test Runner 文件-不执行步骤定义
- sql - SQL嵌套分组问题
- android - 可以创建在 android 中显示邮件应用程序选项的意图吗?
- python - 如何创建一个通过 items() 返回重复值的类 dict 对象
- c++ - 了解`_mm_prefetch`
- r - 如何防止在R中使用polymode markdown导出文档后打开firefox?
- excel - 自动将新工作表名称添加到公式中的 AVERAGE 函数
- c# - foreach 循环后的新行
- python - 对 jupyter 中的命令说不
- spring-cloud-stream - Spring Cloud Stream - 多种功能