#24738: No "m2m_changed" signal sent when deleting related objects
-------------------------------------+-------------------------------------
     Reporter:  No-0n3               |                    Owner:  nobody
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  master
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by No-0n3:

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

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.

 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

--

--
Ticket URL: <https://code.djangoproject.com/ticket/24738#comment:8>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/064.20754504a2db27bf1866447795736e12%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to