Ticket #7539: 7539.on_delete.r11724.2.diff
File 7539.on_delete.r11724.2.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) … … 162 106 163 107 # Since E.f is nullable, we should delete F first (after nulling out 164 108 # 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 109 # temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first 172 110 >>> import django.db.models.sql 173 111 >>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery): 174 ... def clear_related(self, related_field, pk_list): 175 ... print "CLEARING FIELD",related_field.name 176 ... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list) 112 ... def update_batch(self, pk_list, values): 113 ... if values == {'f': None}: 114 ... print "CLEARING FIELD f" 115 ... return super(LoggingUpdateQuery, self).update_batch(pk_list, values) 177 116 >>> original_class = django.db.models.sql.UpdateQuery 178 117 >>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery 179 118 >>> e1.delete() … … 187 126 >>> e2.save() 188 127 189 128 # 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 129 >>> f2.delete() 197 130 CLEARING FIELD f 198 131 -
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 Collector 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) 519 collector = Collector() 520 collector.collect([self]) 521 collector.delete() 574 522 575 # Find all the objects than need to be deleted.576 seen_objs = CollectedObjects()577 self._collect_sub_objects(seen_objs)578 579 # Actually delete the objects.580 delete_objects(seen_objs)581 582 523 delete.alters_data = True 583 524 584 525 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
1 from django.db import connection , transaction1 from django.db import connection 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 5 5 from django.db.models.related import RelatedObject 6 6 from django.db.models.query import QuerySet 7 7 from django.db.models.query_utils import QueryWrapper 8 from django.db.models.deletion import CASCADE 8 9 from django.utils.encoding import smart_unicode 9 10 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ 10 11 from django.utils.functional import curry … … 624 625 manager.add(*value) 625 626 626 627 class ManyToOneRel(object): 627 def __init__(self, to, field_name, related_name=None, 628 limit_choices_to=None, lookup_overrides=None, parent_link=False): 628 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 629 629 try: 630 630 to._meta 631 631 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 635 635 if limit_choices_to is None: 636 636 limit_choices_to = {} 637 637 self.limit_choices_to = limit_choices_to 638 self.lookup_overrides = lookup_overrides or {}639 638 self.multiple = True 640 639 self.parent_link = parent_link 640 self.on_delete = on_delete 641 641 642 642 def is_hidden(self): 643 643 "Should the related object be hidden?" … … 655 655 return data[0] 656 656 657 657 class OneToOneRel(ManyToOneRel): 658 def __init__(self, to, field_name, related_name=None, 659 limit_choices_to=None, lookup_overrides=None, parent_link=False): 658 def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None): 660 659 super(OneToOneRel, self).__init__(to, field_name, 661 660 related_name=related_name, limit_choices_to=limit_choices_to, 662 lookup_overrides=lookup_overrides, parent_link=parent_link) 661 parent_link=parent_link, on_delete=on_delete 662 ) 663 663 self.multiple = False 664 664 665 665 class ManyToManyRel(object): … … 700 700 kwargs['rel'] = rel_class(to, to_field, 701 701 related_name=kwargs.pop('related_name', None), 702 702 limit_choices_to=kwargs.pop('limit_choices_to', None), 703 lookup_overrides=kwargs.pop('lookup_overrides', None), 704 parent_link=kwargs.pop('parent_link', False)) 703 parent_link=kwargs.pop('parent_link', False), 704 on_delete=kwargs.pop('on_delete', CASCADE), 705 ) 705 706 Field.__init__(self, **kwargs) 706 707 707 708 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 connection, transaction, IntegrityError 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): 164 # sort instance collections 165 for instances in self.data.itervalues(): 166 instances.sort(key=lambda obj: obj.pk) 167 168 # if possible, bring the models in an order suitable for databases that don't support transactions 169 # or cannot defer contraint checks until the end of a transaction. 170 self.sort() 171 172 # send pre_delete signals 173 for model, obj in self.instances_with_model(): 174 if not model._meta.auto_created: 175 signals.pre_delete.send(sender=model, instance=obj) 176 177 # update fields 178 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 179 query = sql.UpdateQuery(model, connection) 180 for (field, value), instances in instances_for_fieldvalues.iteritems(): 181 query.update_batch([obj.pk for obj in instances], {field.name: value}) 182 183 # reverse instance collections 184 for instances in self.data.itervalues(): 185 instances.reverse() 186 187 # delete batches 188 for model, batches in self.batches.iteritems(): 189 query = sql.DeleteQuery(model, connection) 190 for field, instances in batches.iteritems(): 191 query.delete_batch([obj.pk for obj in instances], field) 192 193 # delete instances 194 for model, instances in self.data.iteritems(): 195 query = sql.DeleteQuery(model, connection) 196 pk_list = [obj.pk for obj in instances] 197 query.delete_generic_relation_hack(pk_list) 198 query.delete_batch(pk_list) 199 200 # send post_delete signals 201 for model, obj in self.instances_with_model(): 202 if not model._meta.auto_created: 203 signals.post_delete.send(sender=model, instance=obj) 204 205 # update collected instances 206 for model, instances_for_fieldvalues in self.field_updates.iteritems(): 207 for (field, value), instances in instances_for_fieldvalues.iteritems(): 208 for obj in instances: 209 setattr(obj, field.attname, value) 210 for model, instances in self.data.iteritems(): 211 for instance in instances: 212 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 Collector 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. … … 379 380 380 381 # Delete objects in chunks to prevent the list of related objects from 381 382 # becoming too long. 382 seen_objs= None383 collector = None 383 384 while 1: 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 seen_objs = CollectedObjects(seen_objs) 387 for object in del_query[:CHUNK_SIZE]: 388 object._collect_sub_objects(seen_objs) 389 390 if not seen_objs: 387 collector = Collector(collector) 388 collector.collect(del_query[:CHUNK_SIZE]) 389 if not collector: 391 390 break 392 delete_objects(seen_objs)391 collector.delete() 393 392 394 393 # Clear the result cache, in case this QuerySet gets reused. 395 394 self._result_cache = None … … 1000 999 setattr(obj, f.get_cache_name(), rel_obj) 1001 1000 return obj, index_end 1002 1001 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 1002 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 1003 def insert_query(model, values, return_id=False, raw_values=False): 1080 1004 """ 1081 1005 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 -
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: