Opened 9 years ago

Closed 9 years ago

#24738 closed Bug (duplicate)

No "m2m_changed" signal sent when deleting related objects

Reported by: No-0n3 Owned by: nobody
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: No-0n3 Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by No-0n3)

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.

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 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:

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

Change History (9)

comment:1 by Abhaya Agarwal, 9 years ago

Resolution: duplicate
Status: newclosed

This is a duplicate of #6707 which was recently fixed in the master.

comment:2 by No-0n3, 9 years ago

Can you give me an url where I can get this "master" or is it in version 1.9 (devel)?

comment:3 by No-0n3, 9 years ago

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 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 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:

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

Last edited 9 years ago by No-0n3 (previous) (diff)

comment:4 by No-0n3, 9 years ago

Resolution: duplicate
Status: closednew

comment:5 by No-0n3, 9 years ago

Version: 1.8master

comment:6 by No-0n3, 9 years ago

Cc: No-0n3 added
Description: modified (diff)

comment:7 by No-0n3, 9 years ago

Summary: Possible bug in m2m_changed signal when deleting related objectsNo "m2m_changed" signal sent when deleting related objects

comment:8 by No-0n3, 9 years ago

Description: modified (diff)

comment:9 by Abhaya Agarwal, 9 years ago

Resolution: duplicate
Status: newclosed

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.

Note: See TracTickets for help on using tickets.
Back to Top