Ticket #7539: 7539.on_delete.r11724.diff
File 7539.on_delete.r11724.diff, 42.0 KB (added by , 15 years ago) |
---|
-
tests/modeltests/on_delete/__init__.py
1 # 2 No newline at end of file -
tests/modeltests/on_delete/models.py
1 from django.test import TestCase 2 from django.db import models, IntegrityError 3 4 class R(models.Model): 5 is_default = models.BooleanField(default=False) 6 7 def __str__(self): 8 return "%s" % self.pk 9 10 get_default_r = lambda: R.objects.get_or_create(is_default=True)[0] 11 12 class S(models.Model): 13 r = models.ForeignKey(R) 14 15 class T(models.Model): 16 s = models.ForeignKey(S) 17 18 class U(models.Model): 19 t = models.ForeignKey(T) 20 21 22 class A(models.Model): 23 name = models.CharField(max_length=10) 24 25 auto = models.ForeignKey(R, related_name="auto_set") 26 auto_nullable = models.ForeignKey(R, null=True, related_name='auto_nullable_set') 27 setnull = models.ForeignKey(R, on_delete=models.SET_NULL, null=True, related_name='setnull_set') 28 setdefault = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=get_default_r, related_name='setdefault_set') 29 setdefault_none = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=None, null=True, related_name='setnull_nullable_set') 30 cascade = models.ForeignKey(R, on_delete=models.CASCADE, related_name='cascade_set') 31 cascade_nullable = models.ForeignKey(R, on_delete=models.CASCADE, null=True, related_name='cascade_nullable_set') 32 protect = models.ForeignKey(R, on_delete=models.PROTECT, null=True) 33 donothing = models.ForeignKey(R, on_delete=models.DO_NOTHING, null=True, related_name='donothing_set') 34 35 def create_a(name): 36 a = A(name=name) 37 for name in ('auto', 'auto_nullable', 'setnull', 'setdefault', 'setdefault_none', 'cascade', 'cascade_nullable', 'protect', 'donothing'): 38 r = R.objects.create() 39 setattr(a, name, r) 40 a.save() 41 return a 42 43 class M(models.Model): 44 m2m_through = models.ManyToManyField(R, through="MR", related_name="m_through_set") 45 m2m = models.ManyToManyField(R, related_name="m_set") 46 47 class MR(models.Model): 48 m = models.ForeignKey(M) 49 r = models.ForeignKey(R) 50 51 52 class OnDeleteTests(TestCase): 53 def test_basics(self): 54 DEFAULT = get_default_r() 55 56 a = create_a('auto') 57 a.auto.delete() 58 self.failIf(A.objects.filter(name='auto').exists()) 59 60 a = create_a('auto_nullable') 61 a.auto_nullable.delete() 62 self.failUnlessEqual([a], list(A.objects.all())) 63 64 a = create_a('setnull') 65 a.setnull.delete() 66 a = A.objects.get(pk=a.pk) 67 self.failUnlessEqual(None, a.setnull) 68 69 a = create_a('setdefault') 70 a.setdefault.delete() 71 a = A.objects.get(pk=a.pk) 72 self.failUnlessEqual(DEFAULT, a.setdefault) 73 74 a = create_a('setdefault_none') 75 a.setdefault_none.delete() 76 a = A.objects.get(pk=a.pk) 77 self.failUnlessEqual(None, a.setdefault_none) 78 79 a = create_a('cascade') 80 a.cascade.delete() 81 self.failIf(A.objects.filter(name='cascade').exists()) 82 83 a = create_a('cascade_nullable') 84 a.cascade_nullable.delete() 85 self.failIf(A.objects.filter(name='cascade_nullable').exists()) 86 87 a = create_a('protect') 88 self.assertRaises(IntegrityError, a.protect.delete) 89 90 # Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model, 91 # so we connect to pre_delete and set the fk to a known value. 92 replacement_r = R.objects.create() 93 def check_do_nothing(sender, **kwargs): 94 obj = kwargs['instance'] 95 obj.donothing_set.update(donothing=replacement_r) 96 models.signals.pre_delete.connect(check_do_nothing) 97 a = create_a('do_nothing') 98 a.donothing.delete() 99 a = A.objects.get(pk=a.pk) 100 self.failUnlessEqual(replacement_r, a.donothing) 101 models.signals.pre_delete.disconnect(check_do_nothing) 102 103 # cleanup 104 A.objects.all().update(protect=None, donothing=None) 105 R.objects.all().delete() 106 self.failIf(A.objects.exists()) 107 108 def test_m2m(self): 109 m = M.objects.create() 110 r = R.objects.create() 111 MR.objects.create(m=m, r=r) 112 r.delete() 113 self.failIf(MR.objects.exists()) 114 115 r = R.objects.create() 116 MR.objects.create(m=m, r=r) 117 m.delete() 118 self.failIf(MR.objects.exists()) 119 120 m = M.objects.create() 121 r = R.objects.create() 122 m.m2m.add(r) 123 r.delete() 124 through = M._meta.get_field('m2m').rel.through 125 self.failIf(through.objects.exists()) 126 127 r = R.objects.create() 128 m.m2m.add(r) 129 m.delete() 130 self.failIf(through.objects.exists()) 131 132 def assert_num_queries(self, num, func, *args, **kwargs): 133 from django.conf import settings 134 from django.db import connection 135 old_debug = settings.DEBUG 136 settings.DEBUG = True 137 query_count = len(connection.queries) 138 func(*args, **kwargs) 139 self.failUnlessEqual(num, len(connection.queries) - query_count) 140 connection.queries = connection.queries[:query_count] 141 settings.DEBUG = old_debug 142 143 def test_bulk(self): 144 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 145 s = S.objects.create(r=R.objects.create()) 146 for i in xrange(2*GET_ITERATOR_CHUNK_SIZE): 147 T.objects.create(s=s) 148 # 1 (select related `T` instances) 149 # + 1 (select related `U` instances) 150 # + 2 (delete `T` instances in batches) 151 # + 1 (delete `s`) 152 self.assert_num_queries(5, s.delete) 153 self.failIf(S.objects.exists()) 154 155 def test_cache_update(self): 156 deleted = [] 157 related_setnull_sets = [] 158 def pre_delete(sender, **kwargs): 159 obj = kwargs['instance'] 160 deleted.append(obj) 161 if isinstance(obj, R): 162 related_setnull_sets.append(list(a.pk for a in obj.setnull_set.all())) 163 164 models.signals.pre_delete.connect(pre_delete) 165 a = create_a('cache_update_setnull') 166 a.setnull.delete() 167 168 a = create_a('cache_update_setnull') 169 a.cascade.delete() 170 171 for obj in deleted: 172 self.failUnlessEqual(None, obj.pk) 173 174 for pk_list in related_setnull_sets: 175 for a in A.objects.filter(id__in=pk_list): 176 self.failUnlessEqual(None, a.setnull) 177 178 models.signals.pre_delete.disconnect(pre_delete) 179 180 def test_deletion_order(self): 181 pre_delete_order = [] 182 post_delete_order = [] 183 184 def log_post_delete(sender, **kwargs): 185 pre_delete_order.append((sender, kwargs['instance'].pk)) 186 187 def log_pre_delete(sender, **kwargs): 188 post_delete_order.append((sender, kwargs['instance'].pk)) 189 190 models.signals.post_delete.connect(log_post_delete) 191 models.signals.pre_delete.connect(log_pre_delete) 192 193 r = R.objects.create(pk=1) 194 s1 = S.objects.create(pk=1, r=r) 195 s2 = S.objects.create(pk=2, r=r) 196 t1 = T.objects.create(pk=1, s=s1) 197 t2 = T.objects.create(pk=2, s=s2) 198 r.delete() 199 self.failUnlessEqual(pre_delete_order, [(T, 2), (T, 1), (S, 2), (S, 1), (R, 1)]) 200 self.failUnlessEqual(post_delete_order, [(T, 1), (T, 2), (S, 1), (S, 2), (R, 1)]) 201 202 models.signals.post_delete.disconnect(log_post_delete) 203 models.signals.post_delete.disconnect(log_pre_delete) 204 -
tests/modeltests/delete/models.py
44 44 __test__ = {'API_TESTS': """ 45 45 ### Tests for models A,B,C,D ### 46 46 47 ## First, test the CollectedObjects data structure directly48 49 >>> from django.db.models.query import CollectedObjects50 51 >>> g = CollectedObjects()52 >>> g.add("key1", 1, "item1", None)53 False54 >>> g["key1"]55 {1: 'item1'}56 >>> g.add("key2", 1, "item1", "key1")57 False58 >>> g.add("key2", 2, "item2", "key1")59 False60 >>> g["key2"]61 {1: 'item1', 2: 'item2'}62 >>> g.add("key3", 1, "item1", "key1")63 False64 >>> g.add("key3", 1, "item1", "key2")65 True66 >>> g.ordered_keys()67 ['key3', 'key2', 'key1']68 69 >>> g.add("key2", 1, "item1", "key3")70 True71 >>> g.ordered_keys()72 Traceback (most recent call last):73 ...74 CyclicDependency: There is a cyclic dependency of items to be processed.75 76 77 ## Second, test the usage of CollectedObjects by Model.delete()78 79 47 # Due to the way that transactions work in the test harness, 80 48 # doing m.delete() here can work but fail in a real situation, 81 49 # since it may delete all objects, but not in the right order. … … 111 79 >>> c1.save() 112 80 >>> d1 = D(c=c1, a=a1) 113 81 >>> d1.save() 114 115 >>> o = CollectedObjects()116 >>> a1._collect_sub_objects(o)117 >>> o.keys()118 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]119 82 >>> a1.delete() 120 83 121 84 # Same again with a known bad order … … 130 93 >>> c2.save() 131 94 >>> d2 = D(c=c2, a=a2) 132 95 >>> d2.save() 133 134 >>> o = CollectedObjects()135 >>> a2._collect_sub_objects(o)136 >>> o.keys()137 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]138 96 >>> a2.delete() 139 97 140 98 ### Tests for models E,F - nullable related fields ### 141 99 142 ## First, test the CollectedObjects data structure directly143 144 >>> g = CollectedObjects()145 >>> g.add("key1", 1, "item1", None)146 False147 >>> g.add("key2", 1, "item1", "key1", nullable=True)148 False149 >>> g.add("key1", 1, "item1", "key2")150 True151 >>> g.ordered_keys()152 ['key1', 'key2']153 154 ## Second, test the usage of CollectedObjects by Model.delete()155 156 100 >>> e1 = E() 157 101 >>> e1.save() 158 102 >>> f1 = F(e=e1) 159 103 >>> f1.save() 160 104 >>> e1.f = f1 161 105 >>> e1.save() 162 163 # Since E.f is nullable, we should delete F first (after nulling out164 # the E.f field), then E.165 166 >>> o = CollectedObjects()167 >>> e1._collect_sub_objects(o)168 >>> o.keys()169 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]170 171 # temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first172 >>> import django.db.models.sql173 >>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery):174 ... def clear_related(self, related_field, pk_list):175 ... print "CLEARING FIELD",related_field.name176 ... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list)177 >>> original_class = django.db.models.sql.UpdateQuery178 >>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery179 106 >>> e1.delete() 180 CLEARING FIELD f181 107 182 108 >>> e2 = E() 183 109 >>> e2.save() … … 185 111 >>> f2.save() 186 112 >>> e2.f = f2 187 113 >>> e2.save() 188 189 # Same deal as before, though we are starting from the other object.190 191 >>> o = CollectedObjects()192 >>> f2._collect_sub_objects(o)193 >>> o.keys()194 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]195 196 114 >>> f2.delete() 197 CLEARING FIELD f198 199 # Put this back to normal200 >>> django.db.models.sql.UpdateQuery = original_class201 115 """ 202 116 } -
django/db/models/sql/subqueries.py
34 34 self.where = where 35 35 self.execute_sql(None) 36 36 37 def delete_ batch_related(self, pk_list):37 def delete_generic_relation_hack(self, pk_list): 38 38 """ 39 Set up and execute delete queries for all the objects related to the 40 primary key values in pk_list. To delete the objects themselves, use 41 the delete_batch() method. 42 43 More than one physical query may be executed if there are a 44 lot of values in pk_list. 39 Delete objects related to self.model through a GenericRelation. 40 This should be handled by a custom `on_delete` handler in django.contrib.contentttypes. 45 41 """ 46 42 from django.contrib.contenttypes import generic 47 43 cls = self.model 48 for related in cls._meta.get_all_related_many_to_many_objects():49 if not isinstance(related.field, generic.GenericRelation):50 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):51 where = self.where_class()52 where.add((Constraint(None,53 related.field.m2m_reverse_name(), related.field),54 'in',55 pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),56 AND)57 self.do_query(related.field.m2m_db_table(), where)58 59 44 for f in cls._meta.many_to_many: 60 w1 = self.where_class() 61 if isinstance(f, generic.GenericRelation): 62 from django.contrib.contenttypes.models import ContentType 63 field = f.rel.to._meta.get_field(f.content_type_field_name) 64 w1.add((Constraint(None, field.column, field), 'exact', 65 ContentType.objects.get_for_model(cls).id), AND) 45 if not isinstance(f, generic.GenericRelation): 46 continue 47 w1 = self.where_class() 48 from django.contrib.contenttypes.models import ContentType 49 field = f.rel.to._meta.get_field(f.content_type_field_name) 50 w1.add((Constraint(None, field.column, field), 'exact', 51 ContentType.objects.get_for_model(cls).id), AND) 66 52 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 67 53 where = self.where_class() 68 54 where.add((Constraint(None, f.m2m_column_name(), f), 'in', 69 55 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), 70 56 AND) 71 if w1: 72 where.add(w1, AND) 57 where.add(w1, AND) 73 58 self.do_query(f.m2m_db_table(), where) 74 59 75 def delete_batch(self, pk_list ):60 def delete_batch(self, pk_list, field=None): 76 61 """ 77 Set up and execute delete queries for all the objects in pk_list. This 78 should be called after delete_batch_related(), if necessary. 62 Set up and execute delete queries for all the objects in pk_list. 79 63 80 64 More than one physical query may be executed if there are a 81 65 lot of values in pk_list. 82 66 """ 67 if not field: 68 field = self.model._meta.pk 83 69 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 84 70 where = self.where_class() 85 field = self.model._meta.pk86 71 where.add((Constraint(None, field.column, field), 'in', 87 72 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 88 73 self.do_query(self.model._meta.db_table, where) … … 201 186 for alias in self.tables[1:]: 202 187 self.alias_refcount[alias] = 0 203 188 204 def clear_related(self, related_field, pk_list): 205 """ 206 Set up and execute an update query that clears related entries for the 207 keys in pk_list. 208 209 This is used by the QuerySet.delete_objects() method. 210 """ 189 def update_batch(self, pk_list, values): 190 pk_field = self.model._meta.pk 191 self.add_update_values(values) 211 192 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 212 self.where = self.where_class() 213 f = self.model._meta.pk 214 self.where.add((Constraint(None, f.column, f), 'in', 215 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), 216 AND) 217 self.values = [(related_field.column, None, '%s')] 193 self.where = self.where_class() 194 self.where.add((Constraint(None, pk_field.column, pk_field), 'in', pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 218 195 self.execute_sql(None) 219 196 220 197 def add_update_values(self, values): -
django/db/models/base.py
7 7 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 8 8 from django.db.models.fields import AutoField, FieldDoesNotExist 9 9 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 10 from django.db.models.query import delete_objects, Q 11 from django.db.models.query_utils import CollectedObjects, DeferredAttribute 10 from django.db.models.query import Q 11 from django.db.models.query_utils import DeferredAttribute 12 from django.db.models.deletion import CollectedObjects 12 13 from django.db.models.options import Options 13 14 from django.db import connection, transaction, DatabaseError 14 15 from django.db.models import signals … … 513 514 514 515 save_base.alters_data = True 515 516 516 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):517 """518 Recursively populates seen_objs with all objects related to this519 object.520 521 When done, seen_objs.items() will be in the format:522 [(model_class, {pk_val: obj, pk_val: obj, ...}),523 (model_class, {pk_val: obj, pk_val: obj, ...}), ...]524 """525 pk_val = self._get_pk_val()526 if seen_objs.add(self.__class__, pk_val, self, parent, nullable):527 return528 529 for related in self._meta.get_all_related_objects():530 rel_opts_name = related.get_accessor_name()531 if isinstance(related.field.rel, OneToOneRel):532 try:533 sub_obj = getattr(self, rel_opts_name)534 except ObjectDoesNotExist:535 pass536 else:537 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)538 else:539 # To make sure we can access all elements, we can't use the540 # normal manager on the related object. So we work directly541 # with the descriptor object.542 for cls in self.__class__.mro():543 if rel_opts_name in cls.__dict__:544 rel_descriptor = cls.__dict__[rel_opts_name]545 break546 else:547 # in the case of a hidden fkey just skip it, it'll get548 # processed as an m2m549 if not related.field.rel.is_hidden():550 raise AssertionError("Should never get here.")551 else:552 continue553 delete_qs = rel_descriptor.delete_manager(self).all()554 for sub_obj in delete_qs:555 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)556 557 # Handle any ancestors (for the model-inheritance case). We do this by558 # traversing to the most remote parent classes -- those with no parents559 # themselves -- and then adding those instances to the collection. That560 # will include all the child instances down to "self".561 parent_stack = [p for p in self._meta.parents.values() if p is not None]562 while parent_stack:563 link = parent_stack.pop()564 parent_obj = getattr(self, link.name)565 if parent_obj._meta.parents:566 parent_stack.extend(parent_obj._meta.parents.values())567 continue568 # At this point, parent_obj is base class (no ancestor models). So569 # delete it and all its descendents.570 parent_obj._collect_sub_objects(seen_objs)571 572 517 def delete(self): 573 518 assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname) 574 519 575 520 # Find all the objects than need to be deleted. 576 521 seen_objs = CollectedObjects() 577 se lf._collect_sub_objects(seen_objs)522 seen_objs.collect([self]) 578 523 579 524 # Actually delete the objects. 580 delete_objects(seen_objs)525 seen_objs.delete() 581 526 582 527 delete.alters_data = True 583 528 -
django/db/models/options.py
376 376 cache[obj] = parent 377 377 else: 378 378 cache[obj] = model 379 for klass in get_models( ):379 for klass in get_models(include_auto_created=True): 380 380 for f in klass._meta.local_fields: 381 381 if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: 382 382 cache[RelatedObject(f.rel.to, klass, f)] = None -
django/db/models/fields/related.py
1 from django.db import connection, transaction 1 from django.db import connection, transaction, IntegrityError 2 2 from django.db.backends import util 3 3 from django.db.models import signals, get_model 4 4 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist … … 18 18 19 19 RECURSIVE_RELATIONSHIP_CONSTANT = 'self' 20 20 21 def CASCADE(seen_objs, parent_model, related, sub_objs): 22 seen_objs.collect(sub_objs, parent_model) 23 24 def PROTECT(seen_objs, parent_model, related, sub_objs): 25 msg = "Cannot delete some instances of model '%s' because they are referenced through a protected foreign key: '%s.%s'" % ( 26 parent_model.__name__, related.model.__name__, related.field.name 27 ) 28 raise IntegrityError(msg) 29 30 def SET(value): 31 def set_on_delete(seen_objs, parent_model, related, sub_objs): 32 seen_objs.add_field_update(related.field, value, sub_objs) 33 return set_on_delete 34 35 def SET_NULL(seen_objs, parent_model, related, sub_objs): 36 seen_objs.add_field_update(related.field, None, sub_objs) 37 38 def SET_DEFAULT(seen_objs, parent_model, related, sub_objs): 39 seen_objs.add_field_update(related.field, related.field.get_default(), sub_objs) 40 41 def DO_NOTHING(seen_objs, parent_model, related, sub_objs): 42 pass 43 44 21 45 pending_lookups = {} 22 46 23 47 def add_lazy_relation(cls, field, relation, operation): … … 625 649 626 650 class ManyToOneRel(object): 627 651 def __init__(self, to, field_name, related_name=None, 628 limit_choices_to=None, lookup_overrides=None, parent_link=False): 652 limit_choices_to=None, lookup_overrides=None, parent_link=False, 653 on_delete=None): 629 654 try: 630 655 to._meta 631 656 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 638 663 self.lookup_overrides = lookup_overrides or {} 639 664 self.multiple = True 640 665 self.parent_link = parent_link 666 self.on_delete = on_delete 641 667 642 668 def is_hidden(self): 643 669 "Should the related object be hidden?" … … 656 682 657 683 class OneToOneRel(ManyToOneRel): 658 684 def __init__(self, to, field_name, related_name=None, 659 limit_choices_to=None, lookup_overrides=None, parent_link=False): 685 limit_choices_to=None, lookup_overrides=None, parent_link=False, 686 on_delete=None): 660 687 super(OneToOneRel, self).__init__(to, field_name, 661 688 related_name=related_name, limit_choices_to=limit_choices_to, 662 lookup_overrides=lookup_overrides, parent_link=parent_link) 689 lookup_overrides=lookup_overrides, parent_link=parent_link, 690 on_delete=on_delete) 663 691 self.multiple = False 664 692 665 693 class ManyToManyRel(object): … … 696 724 else: 697 725 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) 698 726 kwargs['verbose_name'] = kwargs.get('verbose_name', None) 727 728 on_delete = kwargs.pop('on_delete', None) 729 if on_delete is None: 730 if kwargs.get('null', False): 731 on_delete = SET_NULL 732 else: 733 on_delete = CASCADE 699 734 700 735 kwargs['rel'] = rel_class(to, to_field, 701 736 related_name=kwargs.pop('related_name', None), 702 737 limit_choices_to=kwargs.pop('limit_choices_to', None), 703 738 lookup_overrides=kwargs.pop('lookup_overrides', None), 704 parent_link=kwargs.pop('parent_link', False)) 739 parent_link=kwargs.pop('parent_link', False), 740 on_delete=on_delete) 705 741 Field.__init__(self, **kwargs) 706 742 707 743 self.db_index = True … … 746 782 target = self.rel.to._meta.db_table 747 783 cls._meta.duplicate_targets[self.column] = (target, "o2m") 748 784 785 on_delete = self.rel.on_delete 786 if on_delete == SET_NULL and not self.null: 787 specification = "'on_delete=SET_NULL'" 788 raise ValueError("%s specified for %s '%s.%s', but the field is not nullable." % (specification, type(self).__name__, cls.__name__, name)) 789 if on_delete == SET_DEFAULT and not self.has_default(): 790 specification = "'on_delete=SET_DEFAULT'" 791 raise ValueError("%s specified for %s '%s.%s', but the field has no default value." % (specification, type(self).__name__, cls.__name__, name)) 792 749 793 def contribute_to_related_class(self, cls, related): 750 794 # Internal FK's - i.e., those with a related name ending with '+' - 751 795 # don't get a related descriptor. -
django/db/models/__init__.py
11 11 from django.db.models.fields.subclassing import SubfieldBase 12 12 from django.db.models.fields.files import FileField, ImageField 13 13 from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel 14 from django.db.models.fields.related import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAULT, DO_NOTHING 14 15 from django.db.models import signals 15 16 16 17 # Admin stages. -
django/db/models/deletion.py
1 from django.utils.datastructures import SortedDict 2 from django.utils.functional import wraps 3 from django.db import connection, transaction 4 from django.db.models import signals, sql 5 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 6 7 def ensure_transaction(func): 8 @wraps(func) 9 def decorated(*args, **kwargs): 10 if not transaction.is_managed(): 11 transaction.enter_transaction_management() 12 forced_managed = True 13 else: 14 forced_managed = False 15 try: 16 func(*args, **kwargs) 17 if forced_managed: 18 transaction.commit() 19 else: 20 transaction.commit_unless_managed() 21 finally: 22 if forced_managed: 23 transaction.leave_transaction_management() 24 return decorated 25 26 class CollectedObjects(object): 27 def __init__(self, previously_seen=None): 28 # {model: {(field, value): set([instances])}} 29 self.field_updates = {} 30 self.data = {} 31 self.batches = {} 32 self.children = {} 33 if previously_seen: 34 self.blocked = set(previously_seen) 35 self.blocked.update(previously_seen.blocked) 36 else: 37 self.blocked = set() 38 39 def add(self, objs, parent_model=None): 40 if not objs: 41 return [] 42 new_objs = [] 43 model = objs[0].__class__ 44 instances = self.data.setdefault(model, []) 45 for obj in objs: 46 if obj not in instances and obj not in self.blocked: 47 new_objs.append(obj) 48 instances.extend(new_objs) 49 # Nullable relationships can be ignored -- they are nulled out before 50 # deleting, and therefore do not affect the order in which objects 51 # have to be deleted. 52 if new_objs and parent_model is not None: 53 self.children.setdefault(parent_model, set()).add(model) 54 return new_objs 55 56 def add_batch(self, related, objs): 57 batches = self.batches.setdefault(related.model, dict()) 58 batches[related.field] = objs 59 60 def add_field_update(self, field, value, objs): 61 if not objs: 62 return 63 model = objs[0].__class__ 64 updates = self.field_updates.setdefault(model, dict()) 65 updates.setdefault((field, value), set()).update(objs) 66 67 def collect(self, instances, parent=None, collect_related=True): 68 new_objs = self.add(instances, parent) 69 if not new_objs: 70 return 71 model = new_objs[0].__class__ 72 73 # Recusively collect parent models, but not their related objects. 74 for parent, ptr in model._meta.parents.items(): 75 if ptr: 76 parent_objs = [getattr(obj, ptr.name) for obj in new_objs] 77 self.collect(parent_objs, model, collect_related=False) 78 79 if collect_related: 80 for related in model._meta.get_all_related_objects(): 81 if related.model._meta.auto_created: 82 self.add_batch(related, new_objs) 83 else: 84 sub_objs = related.model._base_manager.filter(**{"%s__in" % related.field.name: new_objs}) 85 if sub_objs: 86 related.field.rel.on_delete(self, model, related, sub_objs) 87 88 def instances_with_model(self): 89 for model, instances in self.data.iteritems(): 90 for obj in instances: 91 yield model, obj 92 93 def __iter__(self): 94 for model, obj in self.instances_with_model(): 95 yield obj 96 97 def __nonzero__(self): 98 return bool(self.data) 99 100 def sort(self): 101 sorted_models = [] 102 models = self.data.keys() 103 while len(sorted_models) < len(models): 104 found = False 105 for model in models: 106 if model in sorted_models: 107 continue 108 children = self.children.get(model) 109 if not children or not children.difference(sorted_models): 110 sorted_models.append(model) 111 found = True 112 if not found: 113 return 114 self.data = SortedDict([(model, self.data[model]) for model in sorted_models]) 115 116 @ensure_transaction 117 def delete(self): 118 # sort instance collections 119 for instances in self.data.itervalues(): 120 instances.sort(key=lambda obj: obj.pk) 121 122 # if possible, bring models in an order suitable for databases that don't support transactions 123 # or cannot defer contraint checks until the end of a transaction. 124 self.sort() 125 126 # send pre_delete signals 127 for model, obj in self.instances_with_model(): 128 if not model._meta.auto_created: 129 signals.pre_delete.send(sender=model, instance=obj) 130 131 # update fields 132 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 133 query = sql.UpdateQuery(model, connection) 134 for (field, value), instances in instances_for_fieldvalues.iteritems(): 135 query.update_batch([obj.pk for obj in instances], {field.name: value}) 136 137 # reverse instance collections 138 for instances in self.data.itervalues(): 139 instances.reverse() 140 141 # delete batches 142 for model, batches in self.batches.iteritems(): 143 query = sql.DeleteQuery(model, connection) 144 for field, instances in batches.iteritems(): 145 query.delete_batch([obj.pk for obj in instances], field) 146 147 # delete instances 148 for model, instances in self.data.iteritems(): 149 query = sql.DeleteQuery(model, connection) 150 pk_list = [obj.pk for obj in instances] 151 query.delete_generic_relation_hack(pk_list) 152 query.delete_batch(pk_list) 153 154 # send post_delete signals 155 for model, obj in self.instances_with_model(): 156 if not model._meta.auto_created: 157 signals.post_delete.send(sender=model, instance=obj) 158 159 # update collected instances 160 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 161 for (field, value), instances in instances_for_fieldvalues.iteritems(): 162 for obj in instances: 163 setattr(obj, field.attname, value) 164 for model, instances in self.data.iteritems(): 165 for instance in instances: 166 setattr(instance, model._meta.pk.attname, None) -
django/db/models/query.py
6 6 from django.db import connection, transaction, IntegrityError 7 7 from django.db.models.aggregates import Aggregate 8 8 from django.db.models.fields import DateField 9 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory 9 from django.db.models.query_utils import Q, select_related_descend, deferred_class_factory 10 from django.db.models.deletion import CollectedObjects 10 11 from django.db.models import signals, sql 11 12 12 13 # Used to control how many objects are worked with at once in some cases (e.g. … … 384 385 # Collect all the objects to be deleted in this chunk, and all the 385 386 # objects that are related to the objects that are to be deleted. 386 387 seen_objs = CollectedObjects(seen_objs) 387 for object in del_query[:CHUNK_SIZE]: 388 object._collect_sub_objects(seen_objs) 388 seen_objs.collect(del_query[:CHUNK_SIZE]) 389 389 390 390 if not seen_objs: 391 391 break 392 delete_objects(seen_objs)392 seen_objs.delete() 393 393 394 394 # Clear the result cache, in case this QuerySet gets reused. 395 395 self._result_cache = None … … 1000 1000 setattr(obj, f.get_cache_name(), rel_obj) 1001 1001 return obj, index_end 1002 1002 1003 def delete_objects(seen_objs):1004 """1005 Iterate through a list of seen classes, and remove any instances that are1006 referred to.1007 """1008 if not transaction.is_managed():1009 transaction.enter_transaction_management()1010 forced_managed = True1011 else:1012 forced_managed = False1013 try:1014 ordered_classes = seen_objs.keys()1015 except CyclicDependency:1016 # If there is a cyclic dependency, we cannot in general delete the1017 # objects. However, if an appropriate transaction is set up, or if the1018 # database is lax enough, it will succeed. So for now, we go ahead and1019 # try anyway.1020 ordered_classes = seen_objs.unordered_keys()1021 1003 1022 obj_pairs = {}1023 try:1024 for cls in ordered_classes:1025 items = seen_objs[cls].items()1026 items.sort()1027 obj_pairs[cls] = items1028 1029 # Pre-notify all instances to be deleted.1030 for pk_val, instance in items:1031 if not cls._meta.auto_created:1032 signals.pre_delete.send(sender=cls, instance=instance)1033 1034 pk_list = [pk for pk,instance in items]1035 del_query = sql.DeleteQuery(cls, connection)1036 del_query.delete_batch_related(pk_list)1037 1038 update_query = sql.UpdateQuery(cls, connection)1039 for field, model in cls._meta.get_fields_with_model():1040 if (field.rel and field.null and field.rel.to in seen_objs and1041 filter(lambda f: f.column == field.rel.get_related_field().column,1042 field.rel.to._meta.fields)):1043 if model:1044 sql.UpdateQuery(model, connection).clear_related(field,1045 pk_list)1046 else:1047 update_query.clear_related(field, pk_list)1048 1049 # Now delete the actual data.1050 for cls in ordered_classes:1051 items = obj_pairs[cls]1052 items.reverse()1053 1054 pk_list = [pk for pk,instance in items]1055 del_query = sql.DeleteQuery(cls, connection)1056 del_query.delete_batch(pk_list)1057 1058 # Last cleanup; set NULLs where there once was a reference to the1059 # object, NULL the primary key of the found objects, and perform1060 # post-notification.1061 for pk_val, instance in items:1062 for field in cls._meta.fields:1063 if field.rel and field.null and field.rel.to in seen_objs:1064 setattr(instance, field.attname, None)1065 1066 if not cls._meta.auto_created:1067 signals.post_delete.send(sender=cls, instance=instance)1068 setattr(instance, cls._meta.pk.attname, None)1069 1070 if forced_managed:1071 transaction.commit()1072 else:1073 transaction.commit_unless_managed()1074 finally:1075 if forced_managed:1076 transaction.leave_transaction_management()1077 1078 1079 1004 def insert_query(model, values, return_id=False, raw_values=False): 1080 1005 """ 1081 1006 Inserts a new record for the given model. This provides an interface to -
django/db/models/query_utils.py
10 10 from copy import deepcopy 11 11 12 12 from django.utils import tree 13 from django.utils.datastructures import SortedDict14 13 15 14 try: 16 15 sorted 17 16 except NameError: 18 17 from django.utils.itercompat import sorted # For Python 2.3. 19 18 20 21 class CyclicDependency(Exception):22 """23 An error when dealing with a collection of objects that have a cyclic24 dependency, i.e. when deleting multiple objects.25 """26 pass27 28 class CollectedObjects(object):29 """30 A container that stores keys and lists of values along with remembering the31 parent objects for all the keys.32 33 This is used for the database object deletion routines so that we can34 calculate the 'leaf' objects which should be deleted first.35 36 previously_seen is an optional argument. It must be a CollectedObjects37 instance itself; any previously_seen collected object will be blocked from38 being added to this instance.39 """40 41 def __init__(self, previously_seen=None):42 self.data = {}43 self.children = {}44 if previously_seen:45 self.blocked = previously_seen.blocked46 for cls, seen in previously_seen.data.items():47 self.blocked.setdefault(cls, SortedDict()).update(seen)48 else:49 self.blocked = {}50 51 def add(self, model, pk, obj, parent_model, nullable=False):52 """53 Adds an item to the container.54 55 Arguments:56 * model - the class of the object being added.57 * pk - the primary key.58 * obj - the object itself.59 * parent_model - the model of the parent object that this object was60 reached through.61 * nullable - should be True if this relation is nullable.62 63 Returns True if the item already existed in the structure and64 False otherwise.65 """66 if pk in self.blocked.get(model, {}):67 return True68 69 d = self.data.setdefault(model, SortedDict())70 retval = pk in d71 d[pk] = obj72 # Nullable relationships can be ignored -- they are nulled out before73 # deleting, and therefore do not affect the order in which objects74 # have to be deleted.75 if parent_model is not None and not nullable:76 self.children.setdefault(parent_model, []).append(model)77 return retval78 79 def __contains__(self, key):80 return self.data.__contains__(key)81 82 def __getitem__(self, key):83 return self.data[key]84 85 def __nonzero__(self):86 return bool(self.data)87 88 def iteritems(self):89 for k in self.ordered_keys():90 yield k, self[k]91 92 def items(self):93 return list(self.iteritems())94 95 def keys(self):96 return self.ordered_keys()97 98 def ordered_keys(self):99 """100 Returns the models in the order that they should be dealt with (i.e.101 models with no dependencies first).102 """103 dealt_with = SortedDict()104 # Start with items that have no children105 models = self.data.keys()106 while len(dealt_with) < len(models):107 found = False108 for model in models:109 if model in dealt_with:110 continue111 children = self.children.setdefault(model, [])112 if len([c for c in children if c not in dealt_with]) == 0:113 dealt_with[model] = None114 found = True115 if not found:116 raise CyclicDependency(117 "There is a cyclic dependency of items to be processed.")118 119 return dealt_with.keys()120 121 def unordered_keys(self):122 """123 Fallback for the case where is a cyclic dependency but we don't care.124 """125 return self.data.keys()126 127 19 class QueryWrapper(object): 128 20 """ 129 21 A type that indicates the contents are an SQL fragment and the associate