Проблема с ManyToMany Отношения не обновляются сразу после сохранения

У меня возникают проблемы с отношениями ManytoMany, которые не обновляются в модели, когда я ее сохраняю (через администратора) и пытаюсь использовать новое значение в пределах функция, привязанная к сигналу post_save или внутри save_model связанный AdminModel. Я попытался перезагрузить объект внутри этих функций, используя получить функцию с id.. но она все еще имеет старые значения.

Это транзакция? Есть ли сигнал, когда транзакция завершается?

Спасибо,

Ответы

Ответ 1

Когда вы сохраняете модель через формы администратора, это не атомная транзакция. Основной объект сначала сохраняется (чтобы убедиться, что он имеет PK), тогда M2M очищен, а новые значения установлены на все, что вышло из формы. Поэтому, если вы находитесь в файле save() основного объекта, вы находитесь в окне возможностей, где M2M еще не обновлен. На самом деле, если вы попытаетесь что-то сделать для M2M, изменение будет уничтожено с помощью clear(). Я столкнулся с этим примерно год назад.

Код несколько изменился с дней рефакторинга до ORM, но он сводится к коду в django.db.models.fields.ManyRelatedObjectsDescriptor и ReverseManyRelatedObjectsDescriptor. Посмотрите на их методы __set __(), и вы увидите manager.clear(); manager.add(*value) Что clear() полностью очищает любые ссылки M2M для текущего основного объекта в этой таблице. Затем add() устанавливает новые значения.

Итак, чтобы ответить на ваш вопрос: да, это проблема транзакции.

Есть ли сигнал, когда транзакция заканчивается? Ничего официального, но читайте дальше:

Несколько месяцев назад существовал связанный поток, и MonkeyPatching был одним из предложенных методов. Grégoire отправил MonkeyPatch для этого. Я не пробовал, но похоже, что он должен работать.

Ответ 2

У меня есть общее решение, которое кажется немного чище, чем обезглавливание ядра или даже использование сельдерея (хотя я уверен, что кто-то может найти районы, где он терпит неудачу). В основном я добавляю метод clean() в админ для формы, которая имеет отношения m2m, и устанавливает отношения экземпляра к версии cleaned_data. Это делает правильные данные доступными для метода сохранения экземпляра, хотя он еще не "в книгах". Попробуйте и посмотрите, как это получится:

def clean(self, *args, **kwargs):
    # ... actual cleaning here
    # then find the m2m fields and copy from cleaned_data to the instance
    for f in self.instance._meta.get_all_field_names():
        if f in self.cleaned_data:
            field = self.instance._meta.get_field_by_name(f)[0]
            if isinstance(field, ManyToManyField):
                setattr(self.instance,f,self.cleaned_data[f])

Ответ 3

Когда вы пытаетесь получить доступ к полям ManyToMany в сигнале post_save модели, связанные объекты уже удалены и не будут добавлены снова до тех пор, пока сигнал не будет завершен.

Чтобы получить доступ к этим данным, вам необходимо привязать метод save_related к вам ModelAdmin. К сожалению, вам также нужно будет включить код в сигнал post_save для запросов без администратора, требующих вашей настройки.

см. https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

Пример:

# admin.py
Class GroupAdmin(admin.ModelAdmin):
    ...
    def save_related(self, request, form, formsets, change):
        super(GroupAdmin, self).save_related(request, form, formsets, change)
        # do something with the manytomany data from the admin
        form.instance.users.add(some_user)

Затем в ваших сигналах вы можете сделать те же изменения, которые вы хотите выполнить при сохранении:

# signals.py
@receiver(post_save, sender=Group)
def group_post_save(sender, instance, created, **kwargs):
    # do somethign with the manytomany data from non-admin
    instance.users.add(some_user)
    # note that instance.users.all() will be empty from the admin: []

Ответ 4

См. http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

проблема: Когда вы манипулируете m2m модели в приемнике сообщений post или pre_save, ваши изменения уничтожаются при последующей "очистке" m2m от Django.

решение: В обработчике сообщений post или pre_save зарегистрируйте другой обработчик сигнала m2m_changed на промежуточной модели m2m модели, m2m которую вы хотите обновить.

Обратите внимание, что этот второй обработчик получит несколько сигналов m2m_changed, и это ключ для проверки значения аргументов "действия", переданных вместе с ними.

Внутри этого второго обработчика проверьте действие post_clear. Когда вы получаете сигнал с действием post_clear, m2m был очищен Django, и у вас есть шанс успешно его обработать.

пример:

def save_handler(sender, instance, *args, **kwargs):
    m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False)


def m2m_handler(sender, instance, action, *args, **kwargs):
    if action =='post_clear':
        succesfully_manipulate_m2m(instance)


pre_save.connect(save_handler, sender=YouModel, weak=False)

см. https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

Ответ 5

Другой подход без патча Monkey - celery, вы можете сделать задачу, которая будет обращаться к правильным данным для отношения M2M, это потому, что задача выполняется асинхронно, и если вы установите задержку в 30 секунд, aproxim, вы убедитесь, что транзакция завершена.

Вы должны вызывать Task.apply_async(args=[...], countdown=30) в post_save или pre_save.