A signal m2m_changed and bug with post_remove

天大地大妈咪最大 提交于 2020-01-10 03:58:06

问题


I need to detect a post_remove signal, so I have written :

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)

If I change 'post_remove' by 'post_add' it is ok.. Is it a django's bug about post_remove ??

I use that model and I switch beetween two values of 'subscribed' (so one added and one deleted)

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'})

I have seen a post with a bug of django, maybe it havn't been fixed... (or it's me ^^)


回答1:


As I understand it, it's not a bug, it's just that Django does not update m2m relations in the way you expect. It does not remove the relations to be deleted then add the new ones. Instead, it clears all of the m2m relations, then adds them again.

There's a related question Django signal m2m_changed not triggered which links to ticket 13087.

So you can check for the pre_clear or post_clear action with the m2m_changed signal, but since those actions do not provide pk_set, it doesn't help you find the related entries before the save, as you wanted to do in your other question.




回答2:


Thanks to Alasdairs comment I found the solution and will post it here - maybe someone could use it.

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)



回答3:


I had reach a conclusion after long long time o seaching First my problem: I had some how update one atribute from my model to set it False when my m2m is is empty, and true if it have at least 1 item, so, the true thing works but when i try to "pre_remove" or "post_remove" never is trigged, so after some trys with some differents examples i saw something weird on this "pre_clear", each time i change my m2m this always has the last values, so i manage to force delete this values from my m2m and this way it trigger the pre_remove and post_remove, so this works for me, see bellow the code

So now i can automaticly set ativo True or False based on my 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 )


来源:https://stackoverflow.com/questions/11686193/a-signal-m2m-changed-and-bug-with-post-remove

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!