Ticket #7539: 7539.on_delete.diff
File 7539.on_delete.diff, 18.2 KB (added by , 15 years ago) |
---|
-
tests/modeltests/delete/models.py
46 46 47 47 ## First, test the CollectedObjects data structure directly 48 48 49 >>> from django.db.models.query importCollectedObjects49 >>> from django.db.models.query_utils import CollectedFields, CollectedObjects 50 50 51 51 >>> g = CollectedObjects() 52 52 >>> g.add("key1", 1, "item1", None) … … 112 112 >>> d1 = D(c=c1, a=a1) 113 113 >>> d1.save() 114 114 115 >>> o = CollectedObjects()116 >>> a1._collect_sub_objects(o )115 >>> o, f = CollectedObjects(), CollectedFields() 116 >>> a1._collect_sub_objects(o, f) 117 117 >>> o.keys() 118 118 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 119 >>> f.keys() 120 [] 119 121 >>> a1.delete() 120 122 121 123 # Same again with a known bad order … … 131 133 >>> d2 = D(c=c2, a=a2) 132 134 >>> d2.save() 133 135 134 >>> o = CollectedObjects()135 >>> a2._collect_sub_objects(o )136 >>> o, f = CollectedObjects(), CollectedFields() 137 >>> a2._collect_sub_objects(o, f) 136 138 >>> o.keys() 137 139 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 140 >>> f.keys() 141 [] 138 142 >>> a2.delete() 139 143 140 144 ### Tests for models E,F - nullable related fields ### … … 163 167 # Since E.f is nullable, we should delete F first (after nulling out 164 168 # the E.f field), then E. 165 169 166 >>> o = CollectedObjects()167 >>> e1._collect_sub_objects(o )170 >>> o, f = CollectedObjects(), CollectedFields() 171 >>> e1._collect_sub_objects(o, f) 168 172 >>> o.keys() 169 173 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 174 >>> f.keys() 175 [<class 'modeltests.delete.models.E'>] 170 176 171 # temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first172 >>> import django.db.models.sql173 >>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery):174 ... def clear_related(self, related_field, pk_list):175 ... print "CLEARING FIELD",related_field.name176 ... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list)177 >>> original_class = django.db.models.sql.UpdateQuery178 >>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery179 177 >>> e1.delete() 180 CLEARING FIELD f181 178 182 179 >>> e2 = E() 183 180 >>> e2.save() … … 188 185 189 186 # Same deal as before, though we are starting from the other object. 190 187 191 >>> o = CollectedObjects()192 >>> f2._collect_sub_objects(o )188 >>> o, f = CollectedObjects(), CollectedFields() 189 >>> f2._collect_sub_objects(o, f) 193 190 >>> o.keys() 194 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 191 [<class 'modeltests.delete.models.F'>] 192 >>> f.keys() 193 [<class 'modeltests.delete.models.E'>] 195 194 196 195 >>> f2.delete() 197 CLEARING FIELD f198 199 # Put this back to normal200 >>> django.db.models.sql.UpdateQuery = original_class201 196 """ 202 197 } -
django/db/models/base.py
8 8 from django.db.models.fields import AutoField, FieldDoesNotExist 9 9 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 10 10 from django.db.models.query import delete_objects, Q 11 from django.db.models.query_utils import Collected Objects, DeferredAttribute11 from django.db.models.query_utils import CollectedFields, CollectedObjects, DeferredAttribute 12 12 from django.db.models.options import Options 13 from django.db import connection, transaction, DatabaseError 13 from django.db import connection, transaction, DatabaseError, IntegrityError 14 14 from django.db.models import signals 15 from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT, DO_NOTHING 15 16 from django.db.models.loading import register_models, get_model 16 17 from django.utils.functional import curry 17 18 from django.utils.encoding import smart_str, force_unicode, smart_unicode … … 513 514 514 515 save_base.alters_data = True 515 516 516 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):517 def _collect_sub_objects(self, seen_objs, fields_to_set, parent=None, nullable=False): 517 518 """ 518 519 Recursively populates seen_objs with all objects related to this 519 520 object. … … 525 526 pk_val = self._get_pk_val() 526 527 if seen_objs.add(self.__class__, pk_val, self, parent, nullable): 527 528 return 529 530 def _handle_sub_obj(related, sub_obj): 531 on_delete = related.field.rel.on_delete 532 if on_delete is None: 533 #If no explicit on_delete option is specified, use the old 534 #django behavior as the default: SET_NULL if the foreign 535 #key is nullable, otherwise CASCADE. 536 if related.field.null: 537 on_delete = SET_NULL 538 else: 539 on_delete = CASCADE 540 if on_delete == DO_NOTHING: 541 return 542 elif on_delete == CASCADE: 543 sub_obj._collect_sub_objects(seen_objs, fields_to_set, self.__class__) 544 elif on_delete == SET_NULL: 545 fields_to_set.add(sub_obj, related.field, None) 546 elif on_delete == SET_DEFAULT: 547 fields_to_set.add(sub_obj, related.field, related.field.get_default()) 548 elif on_delete == PROTECT: 549 msg = '[Django] Cannot delete a parent object: a foreign key constraint fails (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 550 sub_obj.__class__, 551 sub_obj._get_pk_val(), 552 self.__class__, 553 pk_val, 554 ) 555 raise IntegrityError(msg) 528 556 529 557 for related in self._meta.get_all_related_objects(): 530 558 rel_opts_name = related.get_accessor_name() … … 534 562 except ObjectDoesNotExist: 535 563 pass 536 564 else: 537 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)565 _handle_sub_obj(related, sub_obj) 538 566 else: 539 567 # To make sure we can access all elements, we can't use the 540 568 # normal manager on the related object. So we work directly … … 547 575 raise AssertionError("Should never get here.") 548 576 delete_qs = rel_descriptor.delete_manager(self).all() 549 577 for sub_obj in delete_qs: 550 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)578 _handle_sub_obj(related, sub_obj) 551 579 552 580 # Handle any ancestors (for the model-inheritance case). We do this by 553 581 # traversing to the most remote parent classes -- those with no parents … … 562 590 continue 563 591 # At this point, parent_obj is base class (no ancestor models). So 564 592 # delete it and all its descendents. 565 parent_obj._collect_sub_objects(seen_objs )593 parent_obj._collect_sub_objects(seen_objs, fields_to_set) 566 594 567 595 def delete(self): 568 596 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) 569 597 570 598 # Find all the objects than need to be deleted. 571 599 seen_objs = CollectedObjects() 572 self._collect_sub_objects(seen_objs) 600 fields_to_set = CollectedFields() 601 self._collect_sub_objects(seen_objs, fields_to_set) 573 602 574 603 # Actually delete the objects. 575 delete_objects(seen_objs )604 delete_objects(seen_objs, fields_to_set) 576 605 577 606 delete.alters_data = True 578 607 -
django/db/models/fields/related.py
20 20 21 21 pending_lookups = {} 22 22 23 class CASCADE(object): 24 pass 25 class PROTECT(object): 26 pass 27 class SET_NULL(object): 28 pass 29 class SET_DEFAULT(object): 30 pass 31 class DO_NOTHING(object): 32 pass 33 ALLOWED_ON_DELETE_ACTION_TYPES = set([None, CASCADE, PROTECT, SET_NULL, SET_DEFAULT, DO_NOTHING]) 34 23 35 def add_lazy_relation(cls, field, relation, operation): 24 36 """ 25 37 Adds a lookup on ``cls`` when a related field is defined using a string, … … 218 230 # object you just set. 219 231 setattr(instance, self.cache_name, value) 220 232 setattr(value, self.related.field.get_cache_name(), instance) 233 221 234 222 235 class ReverseSingleRelatedObjectDescriptor(object): 223 236 # This class provides the functionality that makes the related-object … … 628 641 629 642 class ManyToOneRel(object): 630 643 def __init__(self, to, field_name, related_name=None, 631 limit_choices_to=None, lookup_overrides=None, parent_link=False): 644 limit_choices_to=None, lookup_overrides=None, parent_link=False, 645 on_delete=None): 632 646 try: 633 647 to._meta 634 648 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 641 655 self.lookup_overrides = lookup_overrides or {} 642 656 self.multiple = True 643 657 self.parent_link = parent_link 658 self.on_delete = on_delete 644 659 645 660 def get_related_field(self): 646 661 """ … … 655 670 656 671 class OneToOneRel(ManyToOneRel): 657 672 def __init__(self, to, field_name, related_name=None, 658 limit_choices_to=None, lookup_overrides=None, parent_link=False): 673 limit_choices_to=None, lookup_overrides=None, parent_link=False, 674 on_delete=None): 659 675 super(OneToOneRel, self).__init__(to, field_name, 660 676 related_name=related_name, limit_choices_to=limit_choices_to, 661 lookup_overrides=lookup_overrides, parent_link=parent_link) 677 lookup_overrides=lookup_overrides, parent_link=parent_link, 678 on_delete=on_delete) 662 679 self.multiple = False 663 680 664 681 class ManyToManyRel(object): … … 697 714 related_name=kwargs.pop('related_name', None), 698 715 limit_choices_to=kwargs.pop('limit_choices_to', None), 699 716 lookup_overrides=kwargs.pop('lookup_overrides', None), 700 parent_link=kwargs.pop('parent_link', False)) 717 parent_link=kwargs.pop('parent_link', False), 718 on_delete=kwargs.pop('on_delete', None)) 701 719 Field.__init__(self, **kwargs) 702 720 703 721 self.db_index = True … … 742 760 target = self.rel.to._meta.db_table 743 761 cls._meta.duplicate_targets[self.column] = (target, "o2m") 744 762 763 on_delete = self.rel.on_delete 764 if on_delete not in ALLOWED_ON_DELETE_ACTION_TYPES: 765 raise ValueError("Invalid value 'on_delete=%s' specified for %s %s.%s." % (on_delete, type(self).__name__, cls.__name__, name)) 766 if on_delete == SET_NULL and not self.null: 767 specification = "'on_delete=SET_NULL'" 768 raise ValueError("%s specified for %s '%s.%s', but the field is not nullable." % (specification, type(self).__name__, cls.__name__, name)) 769 if on_delete == SET_DEFAULT and not self.has_default(): 770 specification = "'on_delete=SET_DEFAULT'" 771 raise ValueError("%s specified for %s '%s.%s', but the field has no default value." % (specification, type(self).__name__, cls.__name__, name)) 772 745 773 def contribute_to_related_class(self, cls, related): 746 774 setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 747 775 -
django/db/models/__init__.py
11 11 from django.db.models.fields.subclassing import SubfieldBase 12 12 from django.db.models.fields.files import FileField, ImageField 13 13 from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel 14 from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT 14 15 from django.db.models import signals 15 16 16 17 # Admin stages. -
django/db/models/query.py
5 5 from copy import deepcopy 6 6 from django.db import connection, transaction, IntegrityError 7 7 from django.db.models.aggregates import Aggregate 8 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 8 9 from django.db.models.fields import DateField 9 from django.db.models.query_utils import Q, select_related_descend, Collected Objects, CyclicDependency, deferred_class_factory10 from django.db.models.query_utils import Q, select_related_descend, CollectedFields, CollectedObjects, CyclicDependency, deferred_class_factory 10 11 from django.db.models import signals, sql 11 12 12 13 # Used to control how many objects are worked with at once in some cases (e.g. … … 384 385 # Collect all the objects to be deleted in this chunk, and all the 385 386 # objects that are related to the objects that are to be deleted. 386 387 seen_objs = CollectedObjects(seen_objs) 388 fields_to_set = CollectedFields() 387 389 for object in del_query[:CHUNK_SIZE]: 388 object._collect_sub_objects(seen_objs )390 object._collect_sub_objects(seen_objs, fields_to_set) 389 391 390 392 if not seen_objs: 391 393 break 392 delete_objects(seen_objs )394 delete_objects(seen_objs, fields_to_set) 393 395 394 396 # Clear the result cache, in case this QuerySet gets reused. 395 397 self._result_cache = None … … 1000 1002 setattr(obj, f.get_cache_name(), rel_obj) 1001 1003 return obj, index_end 1002 1004 1003 def delete_objects(seen_objs ):1005 def delete_objects(seen_objs, fields_to_set): 1004 1006 """ 1005 1007 Iterate through a list of seen classes, and remove any instances that are 1006 1008 referred to. … … 1021 1023 1022 1024 obj_pairs = {} 1023 1025 try: 1026 fields_to_set.execute_updates() 1027 1024 1028 for cls in ordered_classes: 1025 1029 items = seen_objs[cls].items() 1026 1030 items.sort() … … 1034 1038 del_query = sql.DeleteQuery(cls, connection) 1035 1039 del_query.delete_batch_related(pk_list) 1036 1040 1037 update_query = sql.UpdateQuery(cls, connection)1038 for field, model in cls._meta.get_fields_with_model():1039 if (field.rel and field.null and field.rel.to in seen_objs and1040 filter(lambda f: f.column == field.rel.get_related_field().column,1041 field.rel.to._meta.fields)):1042 if model:1043 sql.UpdateQuery(model, connection).clear_related(field,1044 pk_list)1045 else:1046 update_query.clear_related(field, pk_list)1047 1048 1041 # Now delete the actual data. 1049 1042 for cls in ordered_classes: 1050 1043 items = obj_pairs[cls] … … 1054 1047 del_query = sql.DeleteQuery(cls, connection) 1055 1048 del_query.delete_batch(pk_list) 1056 1049 1057 # Last cleanup; set NULLs where there once was a reference to the 1058 # object, NULL the primary key of the found objects, and perform 1059 # post-notification. 1060 for pk_val, instance in items: 1061 for field in cls._meta.fields: 1062 if field.rel and field.null and field.rel.to in seen_objs: 1063 setattr(instance, field.attname, None) 1050 fields_to_set.update_instances() 1064 1051 1052 for cls in ordered_classes: 1053 items = obj_pairs[cls] 1054 items.reverse() 1055 for pk_val, instance in items: 1065 1056 signals.post_delete.send(sender=cls, instance=instance) 1066 1057 setattr(instance, cls._meta.pk.attname, None) 1067 1058 -
django/db/models/query_utils.py
124 124 """ 125 125 return self.data.keys() 126 126 127 class CollectedFields(object): 128 """ 129 A container that stores the model object and field 130 for fields that need to be set to enforce on_delete=SET_NULL 131 and on_delete=SET_DEFAULT ForeigKey constraints. 132 """ 133 134 def __init__(self): 135 # {model: {(field, value): set([instances])}} 136 self.data = {} 137 138 def add(self, obj, field, value): 139 """ 140 Adds an item. 141 model is the class of the object being added, 142 field is the field to be set, 143 value is the value it needs to be set to. 144 """ 145 d = self.data.setdefault(obj.__class__, dict()) 146 instances = d.setdefault((field, value), set()) 147 instances.add(obj) 148 149 def execute_updates(self): 150 from django.db.models import connection, sql 151 for model, instances_for_fieldvalues in self.data.iteritems(): 152 for (field, value), instances in instances_for_fieldvalues.iteritems(): 153 pk_field = model._meta.pk 154 pk_list = [obj.pk for obj in instances] 155 for offset in range(0, len(pk_list), sql.constants.GET_ITERATOR_CHUNK_SIZE): 156 query = sql.UpdateQuery(model, connection) 157 query.where = query.where_class() 158 query.where.add((sql.where.Constraint(None, pk_field.column, pk_field), 'in', pk_list), sql.where.AND) 159 query.add_update_values({field.name: value}) 160 query.execute_sql() 161 162 def update_instances(self): 163 for model, instances_for_fieldvalues in self.data.iteritems(): 164 for (field, value), instances in instances_for_fieldvalues.iteritems(): 165 for obj in instances: 166 setattr(obj, field.attname, value) 167 168 def __nonzero__(self): 169 return bool(self.data) 170 171 # FIXME: used by the tests, but not really needed: 172 def keys(self): 173 return self.data.keys() 174 175 176 127 177 class QueryWrapper(object): 128 178 """ 129 179 A type that indicates the contents are an SQL fragment and the associate