Сигнал m2m_changed и баг с post_remove

Мне нужно обнаружить сигнал post_remove, поэтому я написал:

def handler1(sender, instance, action, reverse, model, pk_set, **kwargs):
if (action == 'post_remove'):
    test1()  # not declared but make a bug if it works, to detect :)

m2m_changed.connect(handler1, sender=Course.subscribed.through)

Если я изменю «post_remove» на «post_add», все в порядке. Это ошибка django в отношении post_remove ??

Я использую эту модель и переключаюсь между двумя значениями «подписки» (поэтому одно добавлено, а другое удалено)

class Course(models.Model):
    name = models.CharField(max_length=30)
    subscribed = models.ManyToManyField(User, related_name='course_list', blank=True, null=True, limit_choices_to={'userprofile__status': 'student'})

Я видел сообщение с ошибкой django, возможно, она не была исправлена... (или это я ^^)


person nlassaux    schedule 27.07.2012    source источник


Ответы (3)


Насколько я понимаю, это не баг, просто Django не обновляет отношения m2m так, как вы ожидаете. Он не удаляет отношения, которые нужно удалить, а затем добавить новые. Вместо этого он очищает все отношения m2m, а затем добавляет их снова.

Есть связанный с этим вопрос Django signal m2m_changed не активирован, который ссылается на билет 13087.

Таким образом, вы можете проверить действие pre_clear или post_clear с помощью сигнала m2m_changed, но поскольку эти действия не предоставляют pk_set, это не поможет вам найти соответствующие записи перед сохранением, как вы хотели сделать в ваш другой вопрос.

person Alasdair    schedule 27.07.2012
comment
Я действительно не знаю, как это сделать, что мне нужно сделать... Я не могу использовать сигналы, либо перезаписывать .save(). Мне нужно удалить пользователя из поля модели (m2m), если он удален из поле другой модели (м2м)... - person nlassaux; 27.07.2012
comment
Я не уверен в лучшем подходе. Вы можете попробовать сохранить связанные объекты в экземпляре с сигналом pre_save, например. instance._old_m2m=list(instance.subscribed.values_list('pk', flat=True)). Затем в обработчике сигнала post_add сравните pk_set с instance._old_m2m. Удачи! - person Alasdair; 27.07.2012

Благодаря Alasdairs comment Я нашел решение и опубликую его здесь - может быть, кто-то сможет его использовать.

models.py

class Team(models.Model):
    name = models.CharField(max_length=100)
    members = models.ManyToManyField(User)

pre_save.connect(team_pre_save, sender=Team)
m2m_changed.connect(team_members_changed, sender=Team.members.through)

signals.py

def team_pre_save(sender, instance, **kwargs):
    if instance.pk:
        instance._old_m2m = set(list(instance.members.values_list('pk', flat=True)))
    else:
        instance._old_m2m = set(list())

def team_members_changed(sender, instance, **kwargs):
    if kwargs['action'] == "post_clear":
        # remove all users from group
        group = Group.objects.get(name='some group')
        for member in instance._old_m2m:
            user = User.objects.get(pk=member)
            user.groups.remove(group)

    if kwargs['action'] == "post_add":
        added_members = list(kwargs['pk_set'].difference(instance._old_m2m))
        deleted_members = list(instance._old_m2m.difference(kwargs['pk_set']))

        if added_members or deleted_members:
            # we got a change - do something, for example add them to a group?
            group = Group.objects.get(name='some group')

            for member in added_members:
                user = User.objects.get(pk=member)
                user.groups.add(group)

            for member in deleted_members:
                user = User.objects.get(pk=member)
                user.groups.remove(group)
person Thomas Schwärzl    schedule 16.10.2012

Я пришел к выводу после долгого поиска. Сначала моя проблема: мне нужно было как-то обновить один атрибут из моей модели, чтобы установить его False, когда мой m2m пуст, и true, если в нем есть хотя бы 1 элемент, поэтому истина вещь работает, но когда я пытаюсь "pre_remove" или "post_remove" никогда не запускается, поэтому после некоторых попыток с некоторыми разными примерами я увидел что-то странное в этом "pre_clear", каждый раз, когда я меняю свой m2m, это всегда имеет последние значения, поэтому мне удается принудительно удалить эти значения из моего m2m, и таким образом он запускает pre_remove и post_remove, так что это работает для меня, см. ниже код

Итак, теперь я могу автоматически установить Ativo True или False на основе моего m2m.

class Servico(BaseMixin):
    descricao = models.CharField(max_length=50)

#This inheritance from User of django that has is_active boolean field
class UsuarioRM(Usuario):
    servicos = models.ManyToManyField(Servico,related_name='servicos_usuario', blank=True)

# SIGNALS
from django.db.models import signals
from django.db.models.signals import m2m_changed

def usuariorm_servicos_changed(sender, **kwargs):
    action = kwargs.pop('action', None)
    pk_set = kwargs.pop('pk_set', None)
    instance = kwargs.pop('instance', None)

    if action == "pre_clear":
        if instance.servicos.all():
        servicos = instance.servicos.all()
            for servico in servicos:
                instance.servicos.remove(servico)
            instance.save()
    else:
        instance.is_active = False
        instance.save() 
        if action == "post_add":
            if pk_set:
            instance.is_active = True 
        else:
            instance.is_active = False

        instance.save()

 m2m_changed.connect( usuariorm_servicos_changed, sender=UsuarioRM.servicos.through )
person Diego Vinícius    schedule 05.09.2016