Have observed that no m2m_changed signal is sent when a related object is
deleted.
If I explicitly clear the relations with "clear()" or remove the relation
with "remove()", m2m_changed is correctly sent but if I delete it without
calling "clear()" or "remove()" on the m2m field no signal is sent to
indicate a change in relationship.
Is this by design or should a m2m_changed signal be sent when a related
object is deleted from the m2m table at deletion when the relations also
are removed.
Right now I've done a workaround by catching the "pre_delete" signal on my
model that explicitly calls "clear()" on the M2M field to trigger the
signal and catching it with my other signal function when it gets a
"post_clear" action in m2m_changed signal.
Here are my models and signals with my workaround:
{{{
from django.db import models
from django.db.models.signals import m2m_changed, pre_delete
from django.dispatch import receiver
from django.db.models import Count
# Models
class Type(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return unicode(self.name)
class Blacklist(models.Model):
source = models.URLField()
name = models.CharField(max_length=200)
date_added = models.DateField(auto_now_add=True)
last_update = models.DateField(auto_now=True)
types = models.ManyToManyField(Type)
def __unicode__(self):
return unicode(self.name)
class Record(models.Model):
blacklists = models.ManyToManyField(Blacklist)
ip = models.GenericIPAddressField(protocol='IPv4', unique=True)
def __unicode__(self):
return unicode(self.ip)
# Signal actions
@receiver(m2m_changed, sender=Record.blacklists.through)
def blacklists_changed(sender, **kwargs):
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
@receiver(pre_delete, sender=Blacklist)
def blacklist_deleted(sender, **kwargs):
kwargs['instance'].record_set.clear()
}}}
If you comment out the blacklist_deleted and do a <Blacklist
instance>.delete() no m2m_changed signal is sent at all.
BR / Isaac
--
Ticket URL: <https://code.djangoproject.com/ticket/24738>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* status: new => closed
* needs_better_patch: => 0
* resolution: => duplicate
* needs_tests: => 0
* needs_docs: => 0
Comment:
This is a duplicate of #6707 which was recently fixed in the master.
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:1>
Comment (by No-0n3):
Can you give me an url where I can get this "master" or is it in version
1.9 (devel)?
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:2>
Comment (by No-0n3):
Hi again,
I looked at #6707 and it isn't a problem about M2M in admin I'm talking
about. Installed the master branch from github and tested.
My I'm trying to delete all objects from the database that doesn't have
any relation by signaling that the relationship has been changed with the
"m2m_changed" signal.
If I delete a model through the shell with "<object>.delete()", then no
"m2m_changed" signal is sent that any relations has been changed in any
way.
Here is my test:
{{{
Python 2.7.9 (default, Dec 12 2014, 07:23:35)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blacklist.models import Blacklist, Record
>>> l = Blacklist.objects.create(name="test")
>>> r = Record.objects.create(ip="127.0.0.2")
>>> r.blacklists.add(l)
{'reverse': False, 'signal': <django.db.models.signals.ModelSignal object
at 0x7606ad30>, 'instance': <Record: 127.0.0.2>, 'pk_set': set([10L]),
'using': 'default', 'model': <class 'blacklist.models.Blacklist'>,
'action': u'pre_add'}
{'reverse': False, 'signal': <django.db.models.signals.ModelSignal object
at 0x7606ad30>, 'instance': <Record: 127.0.0.2>, 'pk_set': set([10L]),
'using': 'default', 'model': <class 'blacklist.models.Blacklist'>,
'action': u'post_add'}
>>> Record.objects.all()
[<Record: 127.0.0.1>, <Record: 127.0.0.2>]
>>> Record.objects.all()[0].blacklists.add(l)
{'reverse': False, 'signal': <django.db.models.signals.ModelSignal object
at 0x7606ad30>, 'instance': <Record: 127.0.0.1>, 'pk_set': set([10L]),
'using': 'default', 'model': <class 'blacklist.models.Blacklist'>,
'action': u'pre_add'}
{'reverse': False, 'signal': <django.db.models.signals.ModelSignal object
at 0x7606ad30>, 'instance': <Record: 127.0.0.1>, 'pk_set': set([10L]),
'using': 'default', 'model': <class 'blacklist.models.Blacklist'>,
'action': u'post_add'}
>>> Record.objects.all()
[<Record: 127.0.0.1>, <Record: 127.0.0.2>]
>>> Blacklist.objects.all()
[<Blacklist: test>]
>>> Blacklist.objects.all()[0].record_set.all()
[<Record: 127.0.0.1>, <Record: 127.0.0.2>]
>>> l = Blacklist.objects.all()[0]
>>> l
<Blacklist: test>
>>> l.delete()
>>> Record.objects.all()
[<Record: 127.0.0.1>, <Record: 127.0.0.2>]
>>> Record.objects.all()[0].blacklists.all()
[]
>>>
}}}
The signal receiver function:
{{{
@receiver(m2m_changed, sender=Record.blacklists.through)
def blacklists_changed(sender, **kwargs):
print "%s" % kwargs # Only to see if any data was sent and to see if
an m2m_changed signal was received.
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
}}}
No signal is sent at deletetion of a related object to that changed the
relations by deleting the object. Sorry but it isn't a duplication.
Entire code:
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.ip)
print "%s" % kwargs
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
#@receiver(pre_delete, sender=Blacklist)
#def blacklist_deleted(sender, **kwargs):
# kwargs['instance'].record_set.clear()
}}}
BR / Isaac
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:3>
* status: closed => new
* resolution: duplicate =>
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:4>
* version: 1.8 => master
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:5>
* cc: No-0n3 (added)
Old description:
New description:
Hi again,
I looked at #6707 and it isn't a problem about M2M in admin I'm talking
about. Installed the master branch from github and tested.
I'm trying to delete all objects from the database that doesn't have any
The signal receiver function:
{{{
@receiver(m2m_changed, sender=Record.blacklists.through)
def blacklists_changed(sender, **kwargs):
print "%s" % kwargs # Only to see if any data was sent and to see if
an m2m_changed signal was received.
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
}}}
No signal is sent at deletion of a related object when the relation was
broken by deleting the object. Sorry but it isn't a duplication. Because I
don't get any output that the signal function has run as when I ran the
"add"-relation function and the related objects are still there without
any relation to a object. If I uncomment the second function that catch a
"pre_delete" and explicitly call "clear()" on the M2M-field I receive a
"m2m_changed" signal and all records without a related object are deleted.
According to docs "m2m_changed" was a signal that is sent when the
relationship between objects are changed, which happens when the object is
deleted.
Entire code:
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.ip)
print "%s" % kwargs
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
#@receiver(pre_delete, sender=Blacklist)
#def blacklist_deleted(sender, **kwargs):
# kwargs['instance'].record_set.clear()
}}}
BR / Isaac
--
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:6>
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:7>
Old description:
> @receiver(m2m_changed, sender=Record.blacklists.through)
> def blacklists_changed(sender, **kwargs):
> print "%s" % kwargs # Only to see if any data was sent and to see if
> an m2m_changed signal was received.
>
> if kwargs['action'] == "post_clear":
> if kwargs['reverse']:
> Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
> }}}
>
> No signal is sent at deletion of a related object when the relation was
> broken by deleting the object. Sorry but it isn't a duplication. Because
> I don't get any output that the signal function has run as when I ran the
> "add"-relation function and the related objects are still there without
> any relation to a object. If I uncomment the second function that catch a
> "pre_delete" and explicitly call "clear()" on the M2M-field I receive a
> "m2m_changed" signal and all records without a related object are
> deleted. According to docs "m2m_changed" was a signal that is sent when
> the relationship between objects are changed, which happens when the
> object is deleted.
>
> Entire code:
> {{{
> print "%s" % kwargs
>
> if kwargs['action'] == "post_clear":
> if kwargs['reverse']:
> Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
>
> #@receiver(pre_delete, sender=Blacklist)
> #def blacklist_deleted(sender, **kwargs):
> # kwargs['instance'].record_set.clear()
> }}}
>
> BR / Isaac
New description:
Hi,
I looked at #6707 and it isn't a problem about M2M in admin I'm talking
about. Installed the master branch from github and tested. If the solution
in #6707 solved my problem but it didn't.
The signal receiver function:
{{{
@receiver(m2m_changed, sender=Record.blacklists.through)
def blacklists_changed(sender, **kwargs):
print "%s" % kwargs # Only to see if any data was sent and to see if
an m2m_changed signal was received.
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
}}}
No signal is sent at deletion of a related object when the relation was
broken by deleting the object. Sorry but it isn't a duplication. Because I
don't get any output that the signal function has run as when I ran the
"add"-relation function and the related objects are still there without
any relation to a object. If I uncomment the second function that catch a
"pre_delete" and explicitly call "clear()" on the M2M-field I receive a
"m2m_changed" signal and all records without a related object are deleted.
According to docs "m2m_changed" was a signal that is sent when the
relationship between objects are changed, which happens when the object is
deleted.
Entire code:
{{{
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.name)
def __unicode__(self):
return unicode(self.ip)
print "%s" % kwargs
if kwargs['action'] == "post_clear":
if kwargs['reverse']:
Record.objects.annotate(count=Count('blacklists')).exclude(count__gt=0).delete()
#@receiver(pre_delete, sender=Blacklist)
#def blacklist_deleted(sender, **kwargs):
# kwargs['instance'].record_set.clear()
}}}
BR / Isaac
--
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:8>
* status: new => closed
* resolution: => duplicate
Comment:
Thanks for the additional details. #6707 is not related only to admin. It
handles the case where things are removed from m2m relations via direct
assignment or `set`.
The correct duplicate is #17688 which is not yet fixed.
--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:9>