问题
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