Django (v2.0.7) Admin: обратный вызов после фиксации транзакции в БД - PullRequest
0 голосов
/ 25 декабря 2018

У меня огромная модель с множеством атрибутов, включая несколько ManyToManyMapping.Большая часть добавления / обновления в приложении осуществляется через REST API, но для незначительного исправления я использовал Django Admin Form.Эта административная форма также имеет несколько встроенных форм.

Я хочу опубликовать какое-то событие в Kafka (publish_event) после обновления модели либо через форму, либо через REST API.И я хочу, чтобы это произошло, когда транзакция зафиксирована в БД, чтобы службы, прослушивающие события Кафки, не заканчивали получением устаревших данных из БД.

Я сослался на это ТАК , но этопохоже, что он делает это для каждой транзакции, а не для каждой модели, и наличие on_commit создает проблемы, связанные с вызовом дважды (подробнее ниже).

То, что я пробовал до сих пор:

  1. Сигналы: отклонены, так как из-за добавления ManyToManyMapping, model.save() необходимо вызывать дважды, что в итоге привело к публикации 2 событий.Кроме того, он работает с сохранением модели, а не с фиксацией транзакции, поэтому в случае отката я все равно получу публикацию события.

  2. Метод переопределения модели save(self, *args, **kwargs):: Отклонено для того жеПричина, по которой model.save() вызывается дважды.

  3. Переопределение ModelAdmin's save_model: это одна из первых вещей, которая вызывается при нажатии кнопки Сохранить в форме, поэтому переопределение не помогаетпотому что формы еще не обработаны.Таким образом, полное состояние, включая сопоставления M2M, не фиксируется в БД.

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

Переопределение ModelAdmin save_related: Сначала это казалось решением, но опять-таки транзакция еще не зафиксирована в БД. 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 голосов
/ 25 декабря 2018

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)

, который вызывается 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 просто ожидает ответ в ответ.


РЕДАКТИРОВАТЬ : пробовал 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))
...