Ticket #6870: 6870.pre_delete.r13248.diff

File 6870.pre_delete.r13248.diff, 8.3 KB (added by cgieringer, 15 years ago)

Removed "".format to make Python 2.4 compliant

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 6304e00..3a4d5c2 100644
    a b class Model(object):  
    544544
    545545    save_base.alters_data = True
    546546
    547     def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
     547    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False, pre_signal=None):
    548548        """
    549549        Recursively populates seen_objs with all objects related to this
    550550        object.
    class Model(object):  
    553553            [(model_class, {pk_val: obj, pk_val: obj, ...}),
    554554             (model_class, {pk_val: obj, pk_val: obj, ...}), ...]
    555555        """
     556        if not pre_signal is None and not self.__class__._meta.auto_created:
     557            pre_signal.send(sender=self.__class__, instance=self)
    556558        pk_val = self._get_pk_val()
    557559        if seen_objs.add(self.__class__, pk_val, self,
    558560                         type(parent), parent, nullable):
    class Model(object):  
    566568                except ObjectDoesNotExist:
    567569                    pass
    568570                else:
    569                     sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
     571                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal)
    570572            else:
    571573                # To make sure we can access all elements, we can't use the
    572574                # normal manager on the related object. So we work directly
    class Model(object):  
    584586                        continue
    585587                delete_qs = rel_descriptor.delete_manager(self).all()
    586588                for sub_obj in delete_qs:
    587                     sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
     589                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal)
    588590
    589591        for related in self._meta.get_all_related_many_to_many_objects():
    590592            if related.field.rel.through:
    class Model(object):  
    594596                nullable = opts.get_field(reverse_field_name).null
    595597                filters = {reverse_field_name: self}
    596598                for sub_obj in related.field.rel.through._base_manager.using(db).filter(**filters):
    597                     sub_obj._collect_sub_objects(seen_objs, self, nullable)
     599                    sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal)
    598600
    599601        for f in self._meta.many_to_many:
    600602            if f.rel.through:
    class Model(object):  
    604606                nullable = opts.get_field(field_name).null
    605607                filters = {field_name: self}
    606608                for sub_obj in f.rel.through._base_manager.using(db).filter(**filters):
    607                     sub_obj._collect_sub_objects(seen_objs, self, nullable)
     609                    sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal)
    608610            else:
    609611                # m2m-ish but with no through table? GenericRelation: cascade delete
    610612                for sub_obj in f.value_from_object(self).all():
    611613                    # Generic relations not enforced by db constraints, thus we can set
    612614                    # nullable=True, order does not matter
    613                     sub_obj._collect_sub_objects(seen_objs, self, True)
     615                    sub_obj._collect_sub_objects(seen_objs, self, True, pre_signal)
    614616
    615617        # Handle any ancestors (for the model-inheritance case). We do this by
    616618        # traversing to the most remote parent classes -- those with no parents
    class Model(object):  
    625627                continue
    626628            # At this point, parent_obj is base class (no ancestor models). So
    627629            # delete it and all its descendents.
    628             parent_obj._collect_sub_objects(seen_objs)
     630            parent_obj._collect_sub_objects(seen_objs, pre_signal=pre_signal)
    629631
    630632    def delete(self, using=None):
    631633        using = using or router.db_for_write(self.__class__, instance=self)
    class Model(object):  
    633635
    634636        # Find all the objects than need to be deleted.
    635637        seen_objs = CollectedObjects()
    636         self._collect_sub_objects(seen_objs)
     638        self._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete)
    637639
    638640        # Actually delete the objects.
    639641        delete_objects(seen_objs, using)
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index d9fbd9b..b9947b7 100644
    a b class QuerySet(object):  
    438438            # need to maintain the query cache on del_query (see #12328)
    439439            seen_objs = CollectedObjects(seen_objs)
    440440            for i, obj in izip(xrange(CHUNK_SIZE), del_itr):
    441                 obj._collect_sub_objects(seen_objs)
     441                obj._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete)
    442442
    443443            if not seen_objs:
    444444                break
    def delete_objects(seen_objs, using):  
    13081308            items.sort()
    13091309            obj_pairs[cls] = items
    13101310
    1311             # Pre-notify all instances to be deleted.
    1312             for pk_val, instance in items:
    1313                 if not cls._meta.auto_created:
    1314                     signals.pre_delete.send(sender=cls, instance=instance)
    1315 
    13161311            pk_list = [pk for pk,instance in items]
    13171312
    13181313            update_query = sql.UpdateQuery(cls)
  • tests/modeltests/signals/models.py

    diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py
    index ea8137f..936c476 100644
    a b class Person(models.Model):  
    1111    def __unicode__(self):
    1212        return u"%s %s" % (self.first_name, self.last_name)
    1313
     14class BlogPost(models.Model):
     15    author = models.ForeignKey(Person, related_name="posts", null=True)
     16    post = models.TextField()
     17   
     18    def __unicode__(self):
     19        max_len = 128
     20        if len(self.post) > max_len:
     21            post_unicode = self.post[:max_len-len('...')] + '...'
     22        else:
     23            post_unicode = self.post
     24        return u"%s: %s" % (unicode(self.author), post_unicode)
     25
    1426def pre_save_test(signal, sender, instance, **kwargs):
    1527    print 'pre_save signal,', instance
    1628    if kwargs.get('raw'):
  • tests/modeltests/signals/tests.py

    diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
    index 329636c..2a8f748 100644
    a b  
    11from django.db.models import signals
    22from django.test import TestCase
    3 from modeltests.signals.models import Person
     3from modeltests.signals.models import Person, BlogPost
    44
    55class MyReceiver(object):
    66    def __init__(self, param):
    class MyReceiver(object):  
    1111        self._run = True
    1212        signal.disconnect(receiver=self, sender=sender)
    1313
     14def clear_nullable_related(signal, sender, instance, **kwargs):
     15    """
     16    Clears any nullable foreign key fields on related objects.
     17    This simulates ON DELETE SET NULL behavior manually.
     18    """
     19    model = instance #didactic rename
     20    for related in model._meta.get_all_related_objects():
     21        if related.field.null:
     22            accessor = related.get_accessor_name()
     23            try:
     24                related_set = getattr(model, accessor)
     25            except: # some version of DoesNotExist
     26                pass
     27            else:
     28                related_set.clear()
     29
     30def is_deleted(model):
     31    return model.__class__.objects.filter(pk=model.pk).count() == 0
     32
    1433class SignalTests(TestCase):
    1534    def test_disconnect_in_dispatch(self):
    1635        """
    class SignalTests(TestCase):  
    2544        self.failUnless(a._run)
    2645        self.failUnless(b._run)
    2746        self.assertEqual(signals.post_save.receivers, [])
    28        
     47
     48    def test_pre_delete_sent_before_related_objects_cached(self):
     49        """
     50        Test that the pre_delete signal is sent for a model before its related
     51        objects are cached for deletion.  The most common usage for this is to
     52        allow the listener to clear related models so that they are not
     53        cascade deleted.
     54        """
     55        signals.pre_delete.connect(sender=Person, receiver=clear_nullable_related)
     56        person = Person.objects.create(first_name='Zeddicus', last_name='Zorander')
     57        blog_post = BlogPost.objects.create(author=person, post="The life and times of an ornery wizard.")
     58        person.delete()
     59        self.assertTrue(not is_deleted(blog_post))
Back to Top