我有一个巨大的模型,具有很多属性,其中包括多个ManyToManyMapping。应用程序中的大多数添加/更新操作都是通过REST API进行的,但为了进行较小的更正,我使用了Django Admin Form。此管理表单还具有多个内联表单集。
在通过表单或REST API更新模型之后,我想将一些事件发布到Kafka(publish_event
)。而且我希望在将事务提交给DB时发生这种情况,以使侦听Kafka事件的服务最终不会从DB中获取过时的数据。
我提到了这个SO post,但它似乎不是在每个交易中都基于模型进行的,而让on_commit出现了两次被调用的问题(详情请参见下文)。
到目前为止我已经尝试过的事情:
信号:
由于添加了ManyToManyMapping而被拒绝,model.save()
需要被调用两次,最终导致发布了2个事件。而且,它是在模型保存而不是事务提交上运行的,因此,在回滚的情况下,我仍然会以发布事件结束。
覆盖模型的save(self, *args, **kwargs):
方法:
由于与model.save()
被两次调用相同的原因而被拒绝。
覆盖ModelAdmin的save_model
:
这是我们在窗体上单击“保存”时首先要调用的东西之一,所以覆盖此设置无济于事,因为尚未处理表单集。因此,包括M2M映射在内的完整状态不会在数据库中提交。
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
publish_event()
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()
到目前为止,我还没有弄清任何回调触发的事务后提交。答案 0 :(得分:2)
TLDR:覆盖change_view
深入研究源代码文件django.contrib.admin.option.py
后,看来_changeform_view
中的此代码正在触发保存模型和相关的M2M:
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)
由changeform_view
调用的设置原子事务。这是我要覆盖的内容,以便一旦将事情提交到数据库,便可以执行publish_event
:
@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
和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
之后,只是期望得到响应。