Django(v2.0.7)管理员:回调发布数据库事务提交

时间:2018-12-25 00:28:01

标签: django django-models django-forms django-admin

我有一个巨大的模型,具有很多属性,其中包括多个ManyToManyMapping。应用程序中的大多数添加/更新操作都是通过REST API进行的,但为了进行较小的更正,我使用了Django Admin Form。此管理表单还具有多个内联表单集。

在通过表单或REST API更新模型之后,我想将一些事件发布到Kafka(publish_event)。而且我希望在将事务提交给DB时发生这种情况,以使侦听Kafka事件的服务最终不会从DB中获取过时的数据。

我提到了这个SO post,但它似乎不是在每个交易中都基于模型进行的,而让on_commit出现了两次被调用的问题(详情请参见下文)。

到目前为止我已经尝试过的事情:

  1. 信号: 由于添加了ManyToManyMapping而被拒绝,model.save()需要被调用两次,最终导致发布了2个事件。而且,它是在模型保存而不是事务提交上运行的,因此,在回滚的情况下,我仍然会以发布事件结束。

  2. 覆盖模型的save(self, *args, **kwargs):方法: 由于与model.save()被两次调用相同的原因而被拒绝。

  3. 覆盖ModelAdmin的save_model: 这是我们在窗体上单击“保存”时首先要调用的东西之一,所以覆盖此设置无济于事,因为尚未处理表单集。因此,包括M2M映射在内的完整状态不会在数据库中提交。

def save_model(self, request, obj, form, change): super().save_model(request, obj, form, change) publish_event()

  1. 覆盖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() 到目前为止,我还没有弄清任何回调触发的事务后提交。

1 个答案:

答案 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_viewadd_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之后,只是期望得到响应。