Ticket #12953: 12953_r12599.diff
File 12953_r12599.diff, 9.7 KB (added by , 15 years ago) |
---|
-
django/contrib/admin/util.py
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
a b 108 108 # TODO using a private model API! 109 109 obj._collect_sub_objects(collector) 110 110 111 # TODO This next bit is needed only because GenericRelations are112 # cascade-deleted way down in the internals in113 # DeleteQuery.delete_batch_related, instead of being found by114 # _collect_sub_objects. Refs #12593.115 from django.contrib.contenttypes import generic116 for f in obj._meta.many_to_many:117 if isinstance(f, generic.GenericRelation):118 rel_manager = f.value_from_object(obj)119 for related in rel_manager.all():120 # There's a wierdness here in the case that the121 # generic-related object also has FKs pointing to it122 # from elsewhere. DeleteQuery does not follow those123 # FKs or delete any such objects explicitly (which is124 # probably a bug). Some databases may cascade those125 # deletes themselves, and some won't. So do we report126 # those objects as to-be-deleted? No right answer; for127 # now we opt to report only on objects that Django128 # will explicitly delete, at risk that some further129 # objects will be silently deleted by a130 # referential-integrity-maintaining database.131 collector.add(related.__class__, related.pk, related,132 obj.__class__, obj)133 134 111 perms_needed = set() 135 112 136 113 to_delete = collector.nested(_format_callback, … … 188 165 """ 189 166 model, pk = type(obj), obj._get_pk_val() 190 167 168 # auto-created M2M models don't interest us 169 if model._meta.auto_created: 170 return True 171 191 172 key = model, pk 192 173 193 174 if key in self.seen: -
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py
a b 555 555 556 556 for related in self._meta.get_all_related_objects(): 557 557 rel_opts_name = related.get_accessor_name() 558 if isinstance(related.field.rel, OneToOneRel):558 if not related.field.rel.multiple: 559 559 try: 560 560 sub_obj = getattr(self, rel_opts_name) 561 561 except ObjectDoesNotExist: … … 581 581 for sub_obj in delete_qs: 582 582 sub_obj._collect_sub_objects(seen_objs, self, related.field.null) 583 583 584 for related in self._meta.get_all_related_many_to_many_objects(): 585 if related.field.rel.through: 586 opts = related.field.rel.through._meta 587 reverse_field_name = related.field.m2m_reverse_field_name() 588 nullable = opts.get_field(reverse_field_name).null 589 filters = {reverse_field_name: self} 590 for sub_obj in related.field.rel.through._base_manager.filter(**filters): 591 sub_obj._collect_sub_objects(seen_objs, self, nullable) 592 593 for f in self._meta.many_to_many: 594 if f.rel.through: 595 opts = f.rel.through._meta 596 field_name = f.m2m_field_name() 597 nullable = opts.get_field(field_name).null 598 filters = {field_name: self} 599 for sub_obj in f.rel.through._base_manager.filter(**filters): 600 sub_obj._collect_sub_objects(seen_objs, self, nullable) 601 else: 602 # m2m-ish but with no through table? GenericRelation: cascade delete 603 for sub_obj in f.value_from_object(self).all(): 604 # Generic relations not enforced by db constraints, thus we can set 605 # nullable=True, order does not matter 606 sub_obj._collect_sub_objects(seen_objs, self, True) 607 584 608 # Handle any ancestors (for the model-inheritance case). We do this by 585 609 # traversing to the most remote parent classes -- those with no parents 586 610 # themselves -- and then adding those instances to the collection. That -
django/db/models/query.py
diff --git a/django/db/models/query.py b/django/db/models/query.py
a b 1272 1272 signals.pre_delete.send(sender=cls, instance=instance) 1273 1273 1274 1274 pk_list = [pk for pk,instance in items] 1275 del_query = sql.DeleteQuery(cls)1276 del_query.delete_batch_related(pk_list, using=using)1277 1275 1278 1276 update_query = sql.UpdateQuery(cls) 1279 1277 for field, model in cls._meta.get_fields_with_model(): -
django/db/models/sql/subqueries.py
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
a b 26 26 self.where = where 27 27 self.get_compiler(using).execute_sql(None) 28 28 29 def delete_batch_related(self, pk_list, using):30 """31 Set up and execute delete queries for all the objects related to the32 primary key values in pk_list. To delete the objects themselves, use33 the delete_batch() method.34 35 More than one physical query may be executed if there are a36 lot of values in pk_list.37 """38 from django.contrib.contenttypes import generic39 cls = self.model40 for related in cls._meta.get_all_related_many_to_many_objects():41 if not isinstance(related.field, generic.GenericRelation):42 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):43 where = self.where_class()44 where.add((Constraint(None,45 related.field.m2m_reverse_name(), related.field),46 'in',47 pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),48 AND)49 self.do_query(related.field.m2m_db_table(), where, using=using)50 51 for f in cls._meta.many_to_many:52 w1 = self.where_class()53 db_prep_value = None54 if isinstance(f, generic.GenericRelation):55 from django.contrib.contenttypes.models import ContentType56 ct_field = f.rel.to._meta.get_field(f.content_type_field_name)57 w1.add((Constraint(None, ct_field.column, ct_field), 'exact',58 ContentType.objects.get_for_model(cls).id), AND)59 id_field = f.rel.to._meta.get_field(f.object_id_field_name)60 db_prep_value = id_field.get_db_prep_value61 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):62 where = self.where_class()63 where.add((Constraint(None, f.m2m_column_name(), f), 'in',64 map(db_prep_value,65 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE])),66 AND)67 if w1:68 where.add(w1, AND)69 self.do_query(f.m2m_db_table(), where, using=using)70 71 29 def delete_batch(self, pk_list, using): 72 30 """ 73 Set up and execute delete queries for all the objects in pk_list. This 74 should be called after delete_batch_related(), if necessary. 31 Set up and execute delete queries for all the objects in pk_list. 75 32 76 33 More than one physical query may be executed if there are a 77 34 lot of values in pk_list. -
tests/regressiontests/delete_regress/models.py
diff --git a/tests/regressiontests/delete_regress/models.py b/tests/regressiontests/delete_regress/models.py
a b 3 3 from django.db.models import sql, query 4 4 from django.test import TransactionTestCase 5 5 6 from django.contrib.contenttypes import generic 7 from django.contrib.contenttypes.models import ContentType 8 9 class Award(models.Model): 10 name = models.CharField(max_length=25) 11 object_id = models.PositiveIntegerField() 12 content_type = models.ForeignKey(ContentType) 13 content_object = generic.GenericForeignKey() 14 15 class AwardNote(models.Model): 16 award = models.ForeignKey(Award) 17 note = models.CharField(max_length=100) 18 19 class Person(models.Model): 20 name = models.CharField(max_length=25) 21 awards = generic.GenericRelation(Award) 22 6 23 class Book(models.Model): 7 24 pagecount = models.IntegerField() 8 25 -
new file tests/regressiontests/delete_regress/tests.py
diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/regressiontests/delete_regress/tests.py new file mode 100644
- + 1 from django.test import TestCase 2 3 from models import Award, AwardNote, Person 4 5 class DeleteCascadeTests(TestCase): 6 def test_generic_relation_cascade(self): 7 """ 8 Test that Django cascades deletes through generic-related 9 objects to their reverse relations. 10 11 This might falsely succeed if the database cascades deletes 12 itself immediately; the postgresql_psycopg2 backend does not 13 give such a false success because ForeignKeys are created with 14 DEFERRABLE INITIALLY DEFERRED, so its internal cascade is 15 delayed until transaction commit. 16 17 """ 18 person = Person.objects.create(name='Nelson Mandela') 19 award = Award.objects.create(name='Nobel', content_object=person) 20 note = AwardNote.objects.create(note='a peace prize', 21 award=award) 22 self.assertEquals(AwardNote.objects.count(), 1) 23 person.delete() 24 self.assertEquals(Award.objects.count(), 0) 25 # first two asserts are just sanity checks, this is the kicker: 26 self.assertEquals(AwardNote.objects.count(), 0)