6

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

nlassaux
  • 2,335
  • 2
  • 21
  • 35

3 Answers3

7

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.

Community
  • 1
  • 1
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • I really don't know how to do that I need to do... I can't use signals , either .save() overwriting.. I need to remove an User from a model field(m2m) if he is removed from another model's field(m2m)... – nlassaux Jul 27 '12 at 11:48
  • 4
    I'm not sure of the best approach. You could try storing the related objects on the instance with the `pre_save` signal e.g. `instance._old_m2m=list(instance.subscribed.values_list('pk', flat=True))`. Then in your handler for the `post_add` signal, compare `pk_set` with `instance._old_m2m`. Good luck! – Alasdair Jul 27 '12 at 12:05
6

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)
Community
  • 1
  • 1
Thomas Schwärzl
  • 9,518
  • 6
  • 43
  • 69
2

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 )
Diego Vinícius
  • 2,125
  • 1
  • 12
  • 23