Ticket #7539: 7539.on_delete.r12288.diff
File 7539.on_delete.r12288.diff, 45.8 KB (added by , 15 years ago) |
---|
-
tests/modeltests/invalid_models/models.py
181 181 class UniqueM2M(models.Model): 182 182 """ Model to test for unique ManyToManyFields, which are invalid. """ 183 183 unique_people = models.ManyToManyField( Person, unique=True ) 184 185 class InvalidSetNull(models.Model): 186 fk = models.ForeignKey('self', on_delete=models.SET_NULL) 187 188 class InvalidSetDefault(models.Model): 189 fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT) 184 190 185 191 186 192 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. … … 279 285 invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract. 280 286 invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract. 281 287 invalid_models.uniquem2m: ManyToManyFields cannot be unique. Remove the unique argument on 'unique_people'. 288 invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null. 289 invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value. 282 290 """ -
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) … … 163 107 # Since E.f is nullable, we should delete F first (after nulling out 164 108 # the E.f field), then E. 165 109 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 110 # temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first 172 111 >>> import django.db.models.sql 173 112 >>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery): 174 ... def clear_related(self, related_field, pk_list, using): 175 ... print "CLEARING FIELD",related_field.name 176 ... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using) 113 ... def update_batch(self, pk_list, values, using): 114 ... if values == {'f': None}: 115 ... print "CLEARING FIELD f" 116 ... return super(LoggingUpdateQuery, self).update_batch(pk_list, values, using) 117 177 118 >>> original_class = django.db.models.sql.UpdateQuery 178 119 >>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery 179 120 >>> e1.delete() … … 187 128 >>> e2.save() 188 129 189 130 # 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 131 >>> f2.delete() 197 132 CLEARING FIELD f 198 133 -
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 = models.ManyToManyField(R, related_name="m_set") 45 m2m_through = models.ManyToManyField(R, through="MR", related_name="m_through_set") 46 m2m_through_null = models.ManyToManyField(R, through="MRNull", related_name="m_through_null_set") 47 48 class MR(models.Model): 49 m = models.ForeignKey(M) 50 r = models.ForeignKey(R) 51 52 class MRNull(models.Model): 53 m = models.ForeignKey(M) 54 r = models.ForeignKey(R, null=True, on_delete=models.SET_NULL) 55 56 class OnDeleteTests(TestCase): 57 def test_basics(self): 58 DEFAULT = get_default_r() 59 60 a = create_a('auto') 61 a.auto.delete() 62 self.failIf(A.objects.filter(name='auto').exists()) 63 64 a = create_a('auto_nullable') 65 a.auto_nullable.delete() 66 self.failIf(A.objects.filter(name='auto_nullable').exists()) 67 68 a = create_a('setnull') 69 a.setnull.delete() 70 a = A.objects.get(pk=a.pk) 71 self.failUnlessEqual(None, a.setnull) 72 73 a = create_a('setdefault') 74 a.setdefault.delete() 75 a = A.objects.get(pk=a.pk) 76 self.failUnlessEqual(DEFAULT, a.setdefault) 77 78 a = create_a('setdefault_none') 79 a.setdefault_none.delete() 80 a = A.objects.get(pk=a.pk) 81 self.failUnlessEqual(None, a.setdefault_none) 82 83 a = create_a('cascade') 84 a.cascade.delete() 85 self.failIf(A.objects.filter(name='cascade').exists()) 86 87 a = create_a('cascade_nullable') 88 a.cascade_nullable.delete() 89 self.failIf(A.objects.filter(name='cascade_nullable').exists()) 90 91 a = create_a('protect') 92 self.assertRaises(IntegrityError, a.protect.delete) 93 94 # Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model, 95 # so we connect to pre_delete and set the fk to a known value. 96 replacement_r = R.objects.create() 97 def check_do_nothing(sender, **kwargs): 98 obj = kwargs['instance'] 99 obj.donothing_set.update(donothing=replacement_r) 100 models.signals.pre_delete.connect(check_do_nothing) 101 a = create_a('do_nothing') 102 a.donothing.delete() 103 a = A.objects.get(pk=a.pk) 104 self.failUnlessEqual(replacement_r, a.donothing) 105 models.signals.pre_delete.disconnect(check_do_nothing) 106 107 A.objects.all().update(protect=None, donothing=None) 108 R.objects.all().delete() 109 self.failIf(A.objects.exists()) 110 111 def test_m2m(self): 112 m = M.objects.create() 113 r = R.objects.create() 114 MR.objects.create(m=m, r=r) 115 r.delete() 116 self.failIf(MR.objects.exists()) 117 118 r = R.objects.create() 119 MR.objects.create(m=m, r=r) 120 m.delete() 121 self.failIf(MR.objects.exists()) 122 123 m = M.objects.create() 124 r = R.objects.create() 125 m.m2m.add(r) 126 r.delete() 127 through = M._meta.get_field('m2m').rel.through 128 self.failIf(through.objects.exists()) 129 130 r = R.objects.create() 131 m.m2m.add(r) 132 m.delete() 133 self.failIf(through.objects.exists()) 134 135 m = M.objects.create() 136 r = R.objects.create() 137 MRNull.objects.create(m=m, r=r) 138 r.delete() 139 self.failIf(not MRNull.objects.exists()) 140 self.failIf(m.m2m_through_null.exists()) 141 142 143 def assert_num_queries(self, num, func, *args, **kwargs): 144 from django.conf import settings 145 from django.db import connection 146 old_debug = settings.DEBUG 147 settings.DEBUG = True 148 query_count = len(connection.queries) 149 func(*args, **kwargs) 150 self.failUnlessEqual(num, len(connection.queries) - query_count) 151 connection.queries = connection.queries[:query_count] 152 settings.DEBUG = old_debug 153 154 def test_bulk(self): 155 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 156 s = S.objects.create(r=R.objects.create()) 157 for i in xrange(2*GET_ITERATOR_CHUNK_SIZE): 158 T.objects.create(s=s) 159 # 1 (select related `T` instances) 160 # + 1 (select related `U` instances) 161 # + 2 (delete `T` instances in batches) 162 # + 1 (delete `s`) 163 self.assert_num_queries(5, s.delete) 164 self.failIf(S.objects.exists()) 165 166 def test_instance_update(self): 167 deleted = [] 168 related_setnull_sets = [] 169 def pre_delete(sender, **kwargs): 170 obj = kwargs['instance'] 171 deleted.append(obj) 172 if isinstance(obj, R): 173 related_setnull_sets.append(list(a.pk for a in obj.setnull_set.all())) 174 175 models.signals.pre_delete.connect(pre_delete) 176 a = create_a('update_setnull') 177 a.setnull.delete() 178 179 a = create_a('update_cascade') 180 a.cascade.delete() 181 182 for obj in deleted: 183 self.failUnlessEqual(None, obj.pk) 184 185 for pk_list in related_setnull_sets: 186 for a in A.objects.filter(id__in=pk_list): 187 self.failUnlessEqual(None, a.setnull) 188 189 models.signals.pre_delete.disconnect(pre_delete) 190 191 def test_deletion_order(self): 192 pre_delete_order = [] 193 post_delete_order = [] 194 195 def log_post_delete(sender, **kwargs): 196 pre_delete_order.append((sender, kwargs['instance'].pk)) 197 198 def log_pre_delete(sender, **kwargs): 199 post_delete_order.append((sender, kwargs['instance'].pk)) 200 201 models.signals.post_delete.connect(log_post_delete) 202 models.signals.pre_delete.connect(log_pre_delete) 203 204 r = R.objects.create(pk=1) 205 s1 = S.objects.create(pk=1, r=r) 206 s2 = S.objects.create(pk=2, r=r) 207 t1 = T.objects.create(pk=1, s=s1) 208 t2 = T.objects.create(pk=2, s=s2) 209 r.delete() 210 self.failUnlessEqual(pre_delete_order, [(T, 2), (T, 1), (S, 2), (S, 1), (R, 1)]) 211 self.failUnlessEqual(post_delete_order, [(T, 1), (T, 2), (S, 1), (S, 2), (R, 1)]) 212 213 models.signals.post_delete.disconnect(log_post_delete) 214 models.signals.post_delete.disconnect(log_pre_delete) 215 -
django/db/models/sql/subqueries.py
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):29 def delete_generic_relation_hack(self, pk_list, using): 30 30 """ 31 Set up and execute delete queries for all the objects related to the 32 primary key values in pk_list. To delete the objects themselves, use 33 the delete_batch() method. 34 35 More than one physical query may be executed if there are a 36 lot of values in pk_list. 31 Delete objects related to self.model through a GenericRelation. 32 This should be handled by a custom `on_delete` handler in django.contrib.contentttypes. 37 33 """ 38 34 from django.contrib.contenttypes import generic 39 35 cls = self.model 40 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 36 for f in cls._meta.many_to_many: 37 if not isinstance(f, generic.GenericRelation): 38 continue 52 39 w1 = self.where_class() 53 if isinstance(f, generic.GenericRelation): 54 from django.contrib.contenttypes.models import ContentType 55 field = f.rel.to._meta.get_field(f.content_type_field_name) 56 w1.add((Constraint(None, field.column, field), 'exact', 57 ContentType.objects.get_for_model(cls).id), AND) 40 from django.contrib.contenttypes.models import ContentType 41 field = f.rel.to._meta.get_field(f.content_type_field_name) 42 w1.add((Constraint(None, field.column, field), 'exact', ContentType.objects.get_for_model(cls).id), AND) 58 43 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 59 44 where = self.where_class() 60 45 where.add((Constraint(None, f.m2m_column_name(), f), 'in', 61 46 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), 62 47 AND) 63 if w1: 64 where.add(w1, AND) 48 where.add(w1, AND) 65 49 self.do_query(f.m2m_db_table(), where, using=using) 66 50 67 def delete_batch(self, pk_list, using ):51 def delete_batch(self, pk_list, using, field=None): 68 52 """ 69 Set up and execute delete queries for all the objects in pk_list. This 70 should be called after delete_batch_related(), if necessary. 53 Set up and execute delete queries for all the objects in pk_list. 71 54 72 55 More than one physical query may be executed if there are a 73 56 lot of values in pk_list. 74 57 """ 58 if not field: 59 field = self.model._meta.pk 75 60 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 76 61 where = self.where_class() 77 field = self.model._meta.pk78 62 where.add((Constraint(None, field.column, field), 'in', 79 63 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND) 80 64 self.do_query(self.model._meta.db_table, where, using=using) … … 106 90 related_updates=self.related_updates.copy(), **kwargs) 107 91 108 92 109 def clear_related(self, related_field, pk_list, using): 110 """ 111 Set up and execute an update query that clears related entries for the 112 keys in pk_list. 113 114 This is used by the QuerySet.delete_objects() method. 115 """ 93 def update_batch(self, pk_list, values, using): 94 pk_field = self.model._meta.pk 95 self.add_update_values(values) 116 96 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 117 97 self.where = self.where_class() 118 f = self.model._meta.pk 119 self.where.add((Constraint(None, f.column, f), 'in', 98 self.where.add((Constraint(None, pk_field.column, pk_field), 'in', 120 99 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), 121 100 AND) 122 self.values = [(related_field, None, None)]123 101 self.get_compiler(using).execute_sql(None) 124 102 125 103 def add_update_values(self, values): -
django/db/models/base.py
7 7 from django.core import validators 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 Collector 12 13 from django.db.models.options import Options 13 14 from django.db import connections, router, transaction, DatabaseError, DEFAULT_DB_ALIAS 14 15 from django.db.models import signals … … 535 536 536 537 save_base.alters_data = True 537 538 538 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):539 """540 Recursively populates seen_objs with all objects related to this541 object.542 543 When done, seen_objs.items() will be in the format:544 [(model_class, {pk_val: obj, pk_val: obj, ...}),545 (model_class, {pk_val: obj, pk_val: obj, ...}), ...]546 """547 pk_val = self._get_pk_val()548 if seen_objs.add(self.__class__, pk_val, self, parent, nullable):549 return550 551 for related in self._meta.get_all_related_objects():552 rel_opts_name = related.get_accessor_name()553 if isinstance(related.field.rel, OneToOneRel):554 try:555 sub_obj = getattr(self, rel_opts_name)556 except ObjectDoesNotExist:557 pass558 else:559 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)560 else:561 # To make sure we can access all elements, we can't use the562 # normal manager on the related object. So we work directly563 # with the descriptor object.564 for cls in self.__class__.mro():565 if rel_opts_name in cls.__dict__:566 rel_descriptor = cls.__dict__[rel_opts_name]567 break568 else:569 # in the case of a hidden fkey just skip it, it'll get570 # processed as an m2m571 if not related.field.rel.is_hidden():572 raise AssertionError("Should never get here.")573 else:574 continue575 delete_qs = rel_descriptor.delete_manager(self).all()576 for sub_obj in delete_qs:577 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)578 579 # Handle any ancestors (for the model-inheritance case). We do this by580 # traversing to the most remote parent classes -- those with no parents581 # themselves -- and then adding those instances to the collection. That582 # will include all the child instances down to "self".583 parent_stack = [p for p in self._meta.parents.values() if p is not None]584 while parent_stack:585 link = parent_stack.pop()586 parent_obj = getattr(self, link.name)587 if parent_obj._meta.parents:588 parent_stack.extend(parent_obj._meta.parents.values())589 continue590 # At this point, parent_obj is base class (no ancestor models). So591 # delete it and all its descendents.592 parent_obj._collect_sub_objects(seen_objs)593 594 539 def delete(self, using=None): 595 540 using = using or router.db_for_write(self.__class__, instance=self) 596 541 connection = connections[using] 597 542 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) 598 543 599 # Find all the objects than need to be deleted.600 seen_objs = CollectedObjects()601 self._collect_sub_objects(seen_objs)544 collector = Collector() 545 collector.collect([self]) 546 collector.delete(using=using) 602 547 603 # Actually delete the objects.604 delete_objects(seen_objs, using)605 606 548 delete.alters_data = True 607 549 608 550 def _get_FIELD_display(self, field): -
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
7 7 from django.db.models.related import RelatedObject 8 8 from django.db.models.query import QuerySet 9 9 from django.db.models.query_utils import QueryWrapper 10 from django.db.models.deletion import CASCADE 10 11 from django.utils.encoding import smart_unicode 11 12 from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext 12 13 from django.utils.functional import curry … … 687 688 manager.add(*value) 688 689 689 690 class ManyToOneRel(object): 690 def __init__(self, to, field_name, related_name=None, 691 limit_choices_to=None, lookup_overrides=None, parent_link=False): 691 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 692 692 try: 693 693 to._meta 694 694 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 698 698 if limit_choices_to is None: 699 699 limit_choices_to = {} 700 700 self.limit_choices_to = limit_choices_to 701 self.lookup_overrides = lookup_overrides or {}702 701 self.multiple = True 703 702 self.parent_link = parent_link 703 self.on_delete = on_delete 704 704 705 705 def is_hidden(self): 706 706 "Should the related object be hidden?" … … 718 718 return data[0] 719 719 720 720 class OneToOneRel(ManyToOneRel): 721 def __init__(self, to, field_name, related_name=None, 722 limit_choices_to=None, lookup_overrides=None, parent_link=False): 721 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 723 722 super(OneToOneRel, self).__init__(to, field_name, 724 723 related_name=related_name, limit_choices_to=limit_choices_to, 725 lookup_overrides=lookup_overrides, parent_link=parent_link) 724 parent_link=parent_link, on_delete=on_delete 725 ) 726 726 self.multiple = False 727 727 728 728 class ManyToManyRel(object): … … 771 771 kwargs['rel'] = rel_class(to, to_field, 772 772 related_name=kwargs.pop('related_name', None), 773 773 limit_choices_to=kwargs.pop('limit_choices_to', None), 774 lookup_overrides=kwargs.pop('lookup_overrides', None), 775 parent_link=kwargs.pop('parent_link', False)) 774 parent_link=kwargs.pop('parent_link', False), 775 on_delete=kwargs.pop('on_delete', CASCADE), 776 ) 776 777 Field.__init__(self, **kwargs) 777 778 778 779 self.db_index = True -
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.deletion 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 connections, transaction, IntegrityError, DEFAULT_DB_ALIAS 4 from django.db.models import signals, sql 5 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 6 7 def CASCADE(collector, field, sub_objs): 8 collector.collect(sub_objs, field.rel.to, field.null) 9 if field.null: 10 # FIXME: there should be a connection feature indicating whether nullable related fields should be nulled out before deletion 11 collector.add_field_update(field, None, sub_objs) 12 13 def PROTECT(collector, field, sub_objs): 14 msg = "Cannot delete some instances of model '%s' because they are referenced through a protected foreign key: '%s.%s'" % ( 15 field.rel.to.__name__, sub_objs[0].__class__.__name__, field.name 16 ) 17 raise IntegrityError(msg) 18 19 def SET(value): 20 def set_on_delete(collector, field, sub_objs): 21 collector.add_field_update(field, value, sub_objs) 22 return set_on_delete 23 24 def SET_NULL(collector, field, sub_objs): 25 collector.add_field_update(field, None, sub_objs) 26 27 def SET_DEFAULT(collector, field, sub_objs): 28 collector.add_field_update(field, field.get_default(), sub_objs) 29 30 def DO_NOTHING(collector, field, sub_objs): 31 pass 32 33 def force_managed(func): 34 @wraps(func) 35 def decorated(*args, **kwargs): 36 if not transaction.is_managed(): 37 transaction.enter_transaction_management() 38 forced_managed = True 39 else: 40 forced_managed = False 41 try: 42 func(*args, **kwargs) 43 if forced_managed: 44 transaction.commit() 45 else: 46 transaction.commit_unless_managed() 47 finally: 48 if forced_managed: 49 transaction.leave_transaction_management() 50 return decorated 51 52 class Collector(object): 53 def __init__(self, previously_collected=None): 54 self.data = {} # {model: [instances]} 55 self.batches = {} # {model: {field: set([instances])}} 56 self.field_updates = {} # {model: {(field, value): set([instances])}} 57 self.dependencies = {} # {model: set([models])} 58 if previously_collected: 59 self.blocked = set(previously_collected) 60 self.blocked.update(previously_collected.blocked) 61 else: 62 self.blocked = set() 63 64 def add(self, objs, source=None, nullable=False): 65 """ 66 Adds 'objs' to the collection of objects to be deleted. 67 If the call is the result of a cascade, 'source' should be the model that caused it 68 and 'nullable' should be set to True, if the relation can be null. 69 70 Returns a list of all objects that were not already collected. 71 """ 72 if not objs: 73 return [] 74 new_objs = [] 75 model = objs[0].__class__ 76 instances = self.data.setdefault(model, []) 77 for obj in objs: 78 if obj not in instances and obj not in self.blocked: 79 new_objs.append(obj) 80 instances.extend(new_objs) 81 # Nullable relationships can be ignored -- they are nulled out before 82 # deleting, and therefore do not affect the order in which objects 83 # have to be deleted. 84 if new_objs and source is not None and not nullable: 85 self.dependencies.setdefault(source, set()).add(model) 86 return new_objs 87 88 def add_batch(self, model, field, objs): 89 """ 90 Schedules a batch delete. Every instance of 'model' that related to an instance of 'obj' through 'field' will be deleted. 91 """ 92 self.batches.setdefault(model, {}).setdefault(field, set()).update(objs) 93 94 def add_field_update(self, field, value, objs): 95 """ 96 Schedules a field update. 'objs' must be a homogenous iterable collection of model instances (e.g. a QuerySet). 97 """ 98 objs = list(objs) 99 if not objs: 100 return 101 model = objs[0].__class__ 102 self.field_updates.setdefault(model, {}).setdefault((field, value), set()).update(objs) 103 104 def collect(self, objs, source=None, nullable=False, collect_related=True): 105 """ 106 Adds 'objs' to the collection of objects to be deleted as well as all parent instances. 107 'objs' must be a homogenous iterable collection of model instances (e.g. a QuerySet). 108 If 'collect_related' is True, related objects will be handled by their respective on_delete handler. 109 110 If the call is the result of a cascade, 'source' should be the model that caused it 111 and 'nullable' should be set to True, if the relation can be null. 112 """ 113 new_objs = self.add(objs, source, nullable) 114 if not new_objs: 115 return 116 model = new_objs[0].__class__ 117 118 # Recusively collect parent models, but not their related objects. 119 for parent, ptr in model._meta.parents.items(): 120 if ptr: 121 parent_objs = [getattr(obj, ptr.name) for obj in new_objs] 122 self.collect(parent_objs, model, collect_related=False) 123 124 if collect_related: 125 for related in model._meta.get_all_related_objects(): 126 if related.model._meta.auto_created: 127 self.add_batch(related.model, related.field, new_objs) 128 else: 129 sub_objs = related.model._base_manager.filter(**{"%s__in" % related.field.name: new_objs}) 130 if not sub_objs: 131 continue 132 related.field.rel.on_delete(self, related.field, sub_objs) 133 134 def instances_with_model(self): 135 for model, instances in self.data.iteritems(): 136 for obj in instances: 137 yield model, obj 138 139 def __iter__(self): 140 for model, obj in self.instances_with_model(): 141 yield obj 142 143 def __nonzero__(self): 144 return bool(self.data) 145 146 def sort(self): 147 sorted_models = [] 148 models = self.data.keys() 149 while len(sorted_models) < len(models): 150 found = False 151 for model in models: 152 if model in sorted_models: 153 continue 154 dependencies = self.dependencies.get(model) 155 if not dependencies or not dependencies.difference(sorted_models): 156 sorted_models.append(model) 157 found = True 158 if not found: 159 return 160 self.data = SortedDict([(model, self.data[model]) for model in sorted_models]) 161 162 @force_managed 163 def delete(self, using=None): 164 using = using or DEFAULT_DB_ALIAS 165 # sort instance collections 166 for instances in self.data.itervalues(): 167 instances.sort(key=lambda obj: obj.pk) 168 169 # if possible, bring the models in an order suitable for databases that don't support transactions 170 # or cannot defer contraint checks until the end of a transaction. 171 self.sort() 172 173 # send pre_delete signals 174 for model, obj in self.instances_with_model(): 175 if not model._meta.auto_created: 176 signals.pre_delete.send(sender=model, instance=obj) 177 178 # update fields 179 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 180 query = sql.UpdateQuery(model) 181 for (field, value), instances in instances_for_fieldvalues.iteritems(): 182 query.update_batch([obj.pk for obj in instances], {field.name: value}, using) 183 184 # reverse instance collections 185 for instances in self.data.itervalues(): 186 instances.reverse() 187 188 # delete batches 189 for model, batches in self.batches.iteritems(): 190 query = sql.DeleteQuery(model) 191 for field, instances in batches.iteritems(): 192 query.delete_batch([obj.pk for obj in instances], using, field) 193 194 # delete instances 195 for model, instances in self.data.iteritems(): 196 query = sql.DeleteQuery(model) 197 pk_list = [obj.pk for obj in instances] 198 query.delete_generic_relation_hack(pk_list, using) 199 query.delete_batch(pk_list, using) 200 201 # send post_delete signals 202 for model, obj in self.instances_with_model(): 203 if not model._meta.auto_created: 204 signals.post_delete.send(sender=model, instance=obj) 205 206 # update collected instances 207 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 208 for (field, value), instances in instances_for_fieldvalues.iteritems(): 209 for obj in instances: 210 setattr(obj, field.attname, value) 211 for model, instances in self.data.iteritems(): 212 for instance in instances: 213 setattr(instance, model._meta.pk.attname, None) -
django/db/models/query.py
7 7 from django.db import connections, router, transaction, IntegrityError 8 8 from django.db.models.aggregates import Aggregate 9 9 from django.db.models.fields import DateField 10 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery 10 from django.db.models.query_utils import Q, select_related_descend, deferred_class_factory, InvalidQuery 11 from django.db.models.deletion import Collector 11 12 from django.db.models import signals, sql 12 13 from django.utils.copycompat import deepcopy 13 14 … … 427 428 428 429 # Delete objects in chunks to prevent the list of related objects from 429 430 # becoming too long. 430 seen_objs= None431 collector = None 431 432 while 1: 432 433 # Collect all the objects to be deleted in this chunk, and all the 433 434 # objects that are related to the objects that are to be deleted. 434 seen_objs = CollectedObjects(seen_objs) 435 for object in del_query[:CHUNK_SIZE]: 436 object._collect_sub_objects(seen_objs) 437 438 if not seen_objs: 435 collector = Collector(collector) 436 collector.collect(del_query[:CHUNK_SIZE]) 437 if not collector: 439 438 break 440 delete_objects(seen_objs,del_query.db)439 collector.delete(using=del_query.db) 441 440 442 441 # Clear the result cache, in case this QuerySet gets reused. 443 442 self._result_cache = None … … 1166 1165 setattr(obj, f.get_cache_name(), rel_obj) 1167 1166 return obj, index_end 1168 1167 1169 def delete_objects(seen_objs, using):1170 """1171 Iterate through a list of seen classes, and remove any instances that are1172 referred to.1173 """1174 connection = connections[using]1175 if not transaction.is_managed(using=using):1176 transaction.enter_transaction_management(using=using)1177 forced_managed = True1178 else:1179 forced_managed = False1180 try:1181 ordered_classes = seen_objs.keys()1182 except CyclicDependency:1183 # If there is a cyclic dependency, we cannot in general delete the1184 # objects. However, if an appropriate transaction is set up, or if the1185 # database is lax enough, it will succeed. So for now, we go ahead and1186 # try anyway.1187 ordered_classes = seen_objs.unordered_keys()1188 1168 1189 obj_pairs = {}1190 try:1191 for cls in ordered_classes:1192 items = seen_objs[cls].items()1193 items.sort()1194 obj_pairs[cls] = items1195 1196 # Pre-notify all instances to be deleted.1197 for pk_val, instance in items:1198 if not cls._meta.auto_created:1199 signals.pre_delete.send(sender=cls, instance=instance)1200 1201 pk_list = [pk for pk,instance in items]1202 del_query = sql.DeleteQuery(cls)1203 del_query.delete_batch_related(pk_list, using=using)1204 1205 update_query = sql.UpdateQuery(cls)1206 for field, model in cls._meta.get_fields_with_model():1207 if (field.rel and field.null and field.rel.to in seen_objs and1208 filter(lambda f: f.column == field.rel.get_related_field().column,1209 field.rel.to._meta.fields)):1210 if model:1211 sql.UpdateQuery(model).clear_related(field, pk_list, using=using)1212 else:1213 update_query.clear_related(field, pk_list, using=using)1214 1215 # Now delete the actual data.1216 for cls in ordered_classes:1217 items = obj_pairs[cls]1218 items.reverse()1219 1220 pk_list = [pk for pk,instance in items]1221 del_query = sql.DeleteQuery(cls)1222 del_query.delete_batch(pk_list, using=using)1223 1224 # Last cleanup; set NULLs where there once was a reference to the1225 # object, NULL the primary key of the found objects, and perform1226 # post-notification.1227 for pk_val, instance in items:1228 for field in cls._meta.fields:1229 if field.rel and field.null and field.rel.to in seen_objs:1230 setattr(instance, field.attname, None)1231 1232 if not cls._meta.auto_created:1233 signals.post_delete.send(sender=cls, instance=instance)1234 setattr(instance, cls._meta.pk.attname, None)1235 1236 if forced_managed:1237 transaction.commit(using=using)1238 else:1239 transaction.commit_unless_managed(using=using)1240 finally:1241 if forced_managed:1242 transaction.leave_transaction_management(using=using)1243 1244 1169 class RawQuerySet(object): 1245 1170 """ 1246 1171 Provides an iterator which converts the results of raw SQL queries into -
django/db/models/query_utils.py
10 10 from django.utils.copycompat import deepcopy 11 11 12 12 from django.utils import tree 13 from django.utils.datastructures import SortedDict14 13 15 14 16 class CyclicDependency(Exception):17 """18 An error when dealing with a collection of objects that have a cyclic19 dependency, i.e. when deleting multiple objects.20 """21 pass22 23 15 class InvalidQuery(Exception): 24 16 """ 25 17 The query passed to raw isn't a safe query to use with raw. 26 18 """ 27 19 pass 28 20 29 30 class CollectedObjects(object):31 """32 A container that stores keys and lists of values along with remembering the33 parent objects for all the keys.34 35 This is used for the database object deletion routines so that we can36 calculate the 'leaf' objects which should be deleted first.37 38 previously_seen is an optional argument. It must be a CollectedObjects39 instance itself; any previously_seen collected object will be blocked from40 being added to this instance.41 """42 43 def __init__(self, previously_seen=None):44 self.data = {}45 self.children = {}46 if previously_seen:47 self.blocked = previously_seen.blocked48 for cls, seen in previously_seen.data.items():49 self.blocked.setdefault(cls, SortedDict()).update(seen)50 else:51 self.blocked = {}52 53 def add(self, model, pk, obj, parent_model, nullable=False):54 """55 Adds an item to the container.56 57 Arguments:58 * model - the class of the object being added.59 * pk - the primary key.60 * obj - the object itself.61 * parent_model - the model of the parent object that this object was62 reached through.63 * nullable - should be True if this relation is nullable.64 65 Returns True if the item already existed in the structure and66 False otherwise.67 """68 if pk in self.blocked.get(model, {}):69 return True70 71 d = self.data.setdefault(model, SortedDict())72 retval = pk in d73 d[pk] = obj74 # Nullable relationships can be ignored -- they are nulled out before75 # deleting, and therefore do not affect the order in which objects76 # have to be deleted.77 if parent_model is not None and not nullable:78 self.children.setdefault(parent_model, []).append(model)79 return retval80 81 def __contains__(self, key):82 return self.data.__contains__(key)83 84 def __getitem__(self, key):85 return self.data[key]86 87 def __nonzero__(self):88 return bool(self.data)89 90 def iteritems(self):91 for k in self.ordered_keys():92 yield k, self[k]93 94 def items(self):95 return list(self.iteritems())96 97 def keys(self):98 return self.ordered_keys()99 100 def ordered_keys(self):101 """102 Returns the models in the order that they should be dealt with (i.e.103 models with no dependencies first).104 """105 dealt_with = SortedDict()106 # Start with items that have no children107 models = self.data.keys()108 while len(dealt_with) < len(models):109 found = False110 for model in models:111 if model in dealt_with:112 continue113 children = self.children.setdefault(model, [])114 if len([c for c in children if c not in dealt_with]) == 0:115 dealt_with[model] = None116 found = True117 if not found:118 raise CyclicDependency(119 "There is a cyclic dependency of items to be processed.")120 121 return dealt_with.keys()122 123 def unordered_keys(self):124 """125 Fallback for the case where is a cyclic dependency but we don't care.126 """127 return self.data.keys()128 129 21 class QueryWrapper(object): 130 22 """ 131 23 A type that indicates the contents are an SQL fragment and the associate -
django/core/management/validation.py
22 22 from django.db import models, connection 23 23 from django.db.models.loading import get_app_errors 24 24 from django.db.models.fields.related import RelatedObject 25 from django.db.models.deletion import SET_NULL, SET_DEFAULT 25 26 26 27 e = ModelErrorCollection(outfile) 27 28 … … 66 67 # Perform any backend-specific field validation. 67 68 connection.validation.validate_field(e, opts, f) 68 69 70 # Check if the on_delete behavior is sane 71 if f.rel and hasattr(f.rel, 'on_delete'): 72 if f.rel.on_delete == SET_NULL and not f.null: 73 e.add(opts, "'%s' specifies on_delete=SET_NULL, but cannot be null." % f.name) 74 elif f.rel.on_delete == SET_DEFAULT and not f.has_default(): 75 e.add(opts, "'%s' specifies on_delete=SET_DEFAULT, but has no default value." % f.name) 76 69 77 # Check to see if the related field will clash with any existing 70 78 # fields, m2m fields, m2m related objects or related objects 71 79 if f.rel: