Ticket #7539: 7539.on_delete.r12009.diff
File 7539.on_delete.r12009.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/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 -
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 -
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
6 6 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 7 7 from django.db.models.fields import AutoField, FieldDoesNotExist 8 8 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 9 from django.db.models.query import delete_objects, Q 10 from django.db.models.query_utils import CollectedObjects, DeferredAttribute 9 from django.db.models.query import Q 10 from django.db.models.query_utils import DeferredAttribute 11 from django.db.models.deletion import Collector 11 12 from django.db.models.options import Options 12 13 from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS 13 14 from django.db.models import signals … … 534 535 535 536 save_base.alters_data = True 536 537 537 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):538 """539 Recursively populates seen_objs with all objects related to this540 object.541 542 When done, seen_objs.items() will be in the format:543 [(model_class, {pk_val: obj, pk_val: obj, ...}),544 (model_class, {pk_val: obj, pk_val: obj, ...}), ...]545 """546 pk_val = self._get_pk_val()547 if seen_objs.add(self.__class__, pk_val, self, parent, nullable):548 return549 550 for related in self._meta.get_all_related_objects():551 rel_opts_name = related.get_accessor_name()552 if isinstance(related.field.rel, OneToOneRel):553 try:554 sub_obj = getattr(self, rel_opts_name)555 except ObjectDoesNotExist:556 pass557 else:558 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)559 else:560 # To make sure we can access all elements, we can't use the561 # normal manager on the related object. So we work directly562 # with the descriptor object.563 for cls in self.__class__.mro():564 if rel_opts_name in cls.__dict__:565 rel_descriptor = cls.__dict__[rel_opts_name]566 break567 else:568 # in the case of a hidden fkey just skip it, it'll get569 # processed as an m2m570 if not related.field.rel.is_hidden():571 raise AssertionError("Should never get here.")572 else:573 continue574 delete_qs = rel_descriptor.delete_manager(self).all()575 for sub_obj in delete_qs:576 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)577 578 # Handle any ancestors (for the model-inheritance case). We do this by579 # traversing to the most remote parent classes -- those with no parents580 # themselves -- and then adding those instances to the collection. That581 # will include all the child instances down to "self".582 parent_stack = [p for p in self._meta.parents.values() if p is not None]583 while parent_stack:584 link = parent_stack.pop()585 parent_obj = getattr(self, link.name)586 if parent_obj._meta.parents:587 parent_stack.extend(parent_obj._meta.parents.values())588 continue589 # At this point, parent_obj is base class (no ancestor models). So590 # delete it and all its descendents.591 parent_obj._collect_sub_objects(seen_objs)592 593 538 def delete(self, using=None): 594 using = using or self._state.db or DEFAULT_DB_ALIAS539 using = using or self._state.db 595 540 connection = connections[using] 596 541 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) 597 542 598 # Find all the objects than need to be deleted.599 seen_objs = CollectedObjects()600 self._collect_sub_objects(seen_objs)543 collector = Collector() 544 collector.collect([self]) 545 collector.delete(using=using) 601 546 602 # Actually delete the objects.603 delete_objects(seen_objs, using)604 605 547 delete.alters_data = True 606 548 607 549 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
6 6 from django.db.models.related import RelatedObject 7 7 from django.db.models.query import QuerySet 8 8 from django.db.models.query_utils import QueryWrapper 9 from django.db.models.deletion import CASCADE 9 10 from django.utils.encoding import smart_unicode 10 11 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ 11 12 from django.utils.functional import curry … … 644 645 manager.add(*value) 645 646 646 647 class ManyToOneRel(object): 647 def __init__(self, to, field_name, related_name=None, 648 limit_choices_to=None, lookup_overrides=None, parent_link=False): 648 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 649 649 try: 650 650 to._meta 651 651 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 655 655 if limit_choices_to is None: 656 656 limit_choices_to = {} 657 657 self.limit_choices_to = limit_choices_to 658 self.lookup_overrides = lookup_overrides or {}659 658 self.multiple = True 660 659 self.parent_link = parent_link 660 self.on_delete = on_delete 661 661 662 662 def is_hidden(self): 663 663 "Should the related object be hidden?" … … 675 675 return data[0] 676 676 677 677 class OneToOneRel(ManyToOneRel): 678 def __init__(self, to, field_name, related_name=None, 679 limit_choices_to=None, lookup_overrides=None, parent_link=False): 678 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 680 679 super(OneToOneRel, self).__init__(to, field_name, 681 680 related_name=related_name, limit_choices_to=limit_choices_to, 682 lookup_overrides=lookup_overrides, parent_link=parent_link) 681 parent_link=parent_link, on_delete=on_delete 682 ) 683 683 self.multiple = False 684 684 685 685 class ManyToManyRel(object): … … 725 725 kwargs['rel'] = rel_class(to, to_field, 726 726 related_name=kwargs.pop('related_name', None), 727 727 limit_choices_to=kwargs.pop('limit_choices_to', None), 728 lookup_overrides=kwargs.pop('lookup_overrides', None), 729 parent_link=kwargs.pop('parent_link', False)) 728 parent_link=kwargs.pop('parent_link', False), 729 on_delete=kwargs.pop('on_delete', CASCADE), 730 ) 730 731 Field.__init__(self, **kwargs) 731 732 732 733 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, transaction, IntegrityError, DEFAULT_DB_ALIAS 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 … … 419 420 420 421 # Delete objects in chunks to prevent the list of related objects from 421 422 # becoming too long. 422 seen_objs= None423 collector = None 423 424 while 1: 424 425 # Collect all the objects to be deleted in this chunk, and all the 425 426 # objects that are related to the objects that are to be deleted. 426 seen_objs = CollectedObjects(seen_objs) 427 for object in del_query[:CHUNK_SIZE]: 428 object._collect_sub_objects(seen_objs) 429 430 if not seen_objs: 427 collector = Collector(collector) 428 collector.collect(del_query[:CHUNK_SIZE]) 429 if not collector: 431 430 break 432 delete_objects(seen_objs,del_query.db)431 collector.delete(using=del_query.db) 433 432 434 433 # Clear the result cache, in case this QuerySet gets reused. 435 434 self._result_cache = None … … 1074 1073 setattr(obj, f.get_cache_name(), rel_obj) 1075 1074 return obj, index_end 1076 1075 1077 def delete_objects(seen_objs, using):1078 """1079 Iterate through a list of seen classes, and remove any instances that are1080 referred to.1081 """1082 connection = connections[using]1083 if not transaction.is_managed(using=using):1084 transaction.enter_transaction_management(using=using)1085 forced_managed = True1086 else:1087 forced_managed = False1088 try:1089 ordered_classes = seen_objs.keys()1090 except CyclicDependency:1091 # If there is a cyclic dependency, we cannot in general delete the1092 # objects. However, if an appropriate transaction is set up, or if the1093 # database is lax enough, it will succeed. So for now, we go ahead and1094 # try anyway.1095 ordered_classes = seen_objs.unordered_keys()1096 1076 1097 obj_pairs = {}1098 try:1099 for cls in ordered_classes:1100 items = seen_objs[cls].items()1101 items.sort()1102 obj_pairs[cls] = items1103 1104 # Pre-notify all instances to be deleted.1105 for pk_val, instance in items:1106 if not cls._meta.auto_created:1107 signals.pre_delete.send(sender=cls, instance=instance)1108 1109 pk_list = [pk for pk,instance in items]1110 del_query = sql.DeleteQuery(cls)1111 del_query.delete_batch_related(pk_list, using=using)1112 1113 update_query = sql.UpdateQuery(cls)1114 for field, model in cls._meta.get_fields_with_model():1115 if (field.rel and field.null and field.rel.to in seen_objs and1116 filter(lambda f: f.column == field.rel.get_related_field().column,1117 field.rel.to._meta.fields)):1118 if model:1119 sql.UpdateQuery(model).clear_related(field, pk_list, using=using)1120 else:1121 update_query.clear_related(field, pk_list, using=using)1122 1123 # Now delete the actual data.1124 for cls in ordered_classes:1125 items = obj_pairs[cls]1126 items.reverse()1127 1128 pk_list = [pk for pk,instance in items]1129 del_query = sql.DeleteQuery(cls)1130 del_query.delete_batch(pk_list, using=using)1131 1132 # Last cleanup; set NULLs where there once was a reference to the1133 # object, NULL the primary key of the found objects, and perform1134 # post-notification.1135 for pk_val, instance in items:1136 for field in cls._meta.fields:1137 if field.rel and field.null and field.rel.to in seen_objs:1138 setattr(instance, field.attname, None)1139 1140 if not cls._meta.auto_created:1141 signals.post_delete.send(sender=cls, instance=instance)1142 setattr(instance, cls._meta.pk.attname, None)1143 1144 if forced_managed:1145 transaction.commit(using=using)1146 else:1147 transaction.commit_unless_managed(using=using)1148 finally:1149 if forced_managed:1150 transaction.leave_transaction_management(using=using)1151 1152 1077 class RawQuerySet(object): 1153 1078 """ 1154 1079 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: