Проблема с ManyToMany Relationships не обновляется сразу после сохранения - PullRequest
17 голосов
/ 18 декабря 2009

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

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

Спасибо

Ответы [ 6 ]

25 голосов
/ 18 декабря 2009

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

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

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

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

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

7 голосов
/ 12 февраля 2015

Когда вы пытаетесь получить доступ к полям 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: []
5 голосов
/ 09 августа 2011

У меня есть общее решение для этого, которое кажется немного чище, чем обезьяна, исправляющая ядро ​​или даже использующая сельдерей (хотя я уверен, что кто-то мог бы найти области, где он выходит из строя). По сути, я добавляю метод 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 голосов
/ 06 сентября 2013

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

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

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

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

В этом втором обработчике проверьте действие 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

0 голосов
/ 15 февраля 2019

Одно из решений по обновлению м2, наряду с обновлением одной из ваших моделей.

Django 1.11 and higher

Прежде всего, все запросы через админ-панель являются атомарными. Вы можете посмотреть на ModelAdmin:

@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)

@csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
    with transaction.atomic(using=router.db_for_write(self.model)):
        return self._delete_view(request, object_id, extra_context)

Поведение, которое вы можете наблюдать во время обновления, когда изменения, которые вы сделали с записями m2m, не были сохранены, даже после того, как вы сделали их в методе сохранения одной из ваших моделей или в сигнале, происходит только потому, что форма m2m переписывает все записи после обновления основного объекта.

Вот почему, шаг за шагом:

  1. Основной объект обновлен.

  2. Ваш код (в методе сохранения или в сигнале) внес изменения (вы можете посмотреть их, просто поставив точку останова в ModelAdmin):

 def save_related(self, request, form, formsets, change):
     breakpoint()
     form.save_m2m()
     for formset in formsets:
         self.save_formset(request, form, formset, change=change)
  1. form.save_m2m () принимает все значения m2m, которые были размещены на странице (грубо говоря), и заменяет все записи m2m через связанный менеджер. Вот почему вы не видите свои изменения в конце транзакции.

Есть решение: внесите изменения с помощью m2m через transaction.on_commit. транзакция.on_commit внесет ваши изменения после form.save_m2m (), когда транзакция совершена.

К сожалению, обратная сторона этого решения - ваши изменения с m2m будут выполнены в отдельной транзакции.

0 голосов
/ 18 мая 2011

Вы можете найти больше информации в этой теме: Django много сигналов?

...