Ticket #7539: on_delete_on_update-r11733.diff
File on_delete_on_update-r11733.diff, 45.7 KB (added by , 15 years ago) |
---|
-
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 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 … … 516 517 517 518 save_base.alters_data = True 518 519 519 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):520 def _collect_sub_objects(self, seen_objs, fields_to_set, parent=None, nullable=False): 520 521 """ 521 522 Recursively populates seen_objs with all objects related to this 522 523 object. … … 528 529 pk_val = self._get_pk_val() 529 530 if seen_objs.add(self.__class__, pk_val, self, parent, nullable): 530 531 return 532 533 def _handle_sub_obj(related, sub_obj): 534 on_delete = related.field.rel.on_delete 535 if on_delete is None: 536 #If no explicit on_delete option is specified, use the old 537 #django behavior as the default: SET_NULL if the foreign 538 #key is nullable, otherwise CASCADE. 539 if related.field.null: 540 on_delete = SET_NULL 541 else: 542 on_delete = CASCADE 543 544 if on_delete == CASCADE: 545 sub_obj._collect_sub_objects(seen_objs, fields_to_set, self.__class__) 546 elif on_delete == PROTECT: 547 msg = '[Django] Cannot delete a parent object: a foreign key constraint fails (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 548 sub_obj.__class__, 549 sub_obj._get_pk_val(), 550 self.__class__, 551 pk_val, 552 ) 553 raise IntegrityError(msg) 554 elif on_delete == SET_NULL: 555 if not related.field.null: 556 msg = '[Django] Cannot delete a parent object: foreign key constraint on_delete=SET_NULL is specified for a non-nullable foreign key (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 557 sub_obj.__class__, 558 sub_obj._get_pk_val(), 559 self.__class__, 560 pk_val, 561 ) 562 raise IntegrityError(msg) 563 fields_to_set.add(sub_obj.__class__, sub_obj._get_pk_val(), sub_obj, related.field.name, None) 564 elif on_delete == SET_DEFAULT: 565 if not related.field.has_default(): 566 msg = '[Django] Cannot delete a parent object: foreign key constraint on_delete=SET_DEFAULT is specified for a foreign key with no default value (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 567 sub_obj.__class__, 568 sub_obj._get_pk_val(), 569 self.__class__, 570 pk_val, 571 ) 572 raise IntegrityError(msg) 573 fields_to_set.add(sub_obj.__class__, sub_obj._get_pk_val(), sub_obj, related.field.name, related.field.get_default()) 574 else: 575 raise AttributeError('Unexpected value for on_delete') 531 576 532 577 for related in self._meta.get_all_related_objects(): 533 578 rel_opts_name = related.get_accessor_name() 534 579 if isinstance(related.field.rel, OneToOneRel): 535 580 try: 581 # delattr(self, rel_opts_name) #Delete first to clear any stale cache 582 #TODO: the above line is a bit of a hack 583 #It's one way (not a very good one) to work around stale cache data causing 584 #spurious RESTRICT errors, etc; it would be better to prevent the cache from 585 #becoming stale in the first place. 536 586 sub_obj = getattr(self, rel_opts_name) 537 587 except ObjectDoesNotExist: 538 588 pass 539 589 else: 540 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)590 _handle_sub_obj(related, sub_obj) 541 591 else: 542 592 # To make sure we can access all elements, we can't use the 543 593 # normal manager on the related object. So we work directly … … 555 605 continue 556 606 delete_qs = rel_descriptor.delete_manager(self).all() 557 607 for sub_obj in delete_qs: 558 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)608 _handle_sub_obj(related, sub_obj) 559 609 560 610 # Handle any ancestors (for the model-inheritance case). We do this by 561 611 # traversing to the most remote parent classes -- those with no parents … … 570 620 continue 571 621 # At this point, parent_obj is base class (no ancestor models). So 572 622 # delete it and all its descendents. 573 parent_obj._collect_sub_objects(seen_objs )623 parent_obj._collect_sub_objects(seen_objs, fields_to_set) 574 624 575 625 def delete(self): 576 626 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) 577 627 578 628 # Find all the objects than need to be deleted. 579 629 seen_objs = CollectedObjects() 580 self._collect_sub_objects(seen_objs) 630 fields_to_set = CollectedFields() 631 self._collect_sub_objects(seen_objs, fields_to_set) 581 632 582 633 # Actually delete the objects. 583 delete_objects(seen_objs) 584 634 delete_objects(seen_objs, fields_to_set) 585 635 delete.alters_data = True 586 636 587 637 def _get_FIELD_display(self, field): -
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 ALLOWED_ON_DELETE_ACTION_TYPES = set([None, CASCADE, PROTECT, SET_NULL, SET_DEFAULT]) 32 23 33 def add_lazy_relation(cls, field, relation, operation): 24 34 """ 25 35 Adds a lookup on ``cls`` when a related field is defined using a string, … … 222 232 # object you just set. 223 233 setattr(instance, self.cache_name, value) 224 234 setattr(value, self.related.field.get_cache_name(), instance) 235 236 #TODO: the following function is a bit of a hack 237 #It's one way (not a very good one) to work around stale cache data causing 238 #spurious RESTRICT errors, etc; it would be better to prevent the cache from 239 #becoming stale in the first place. 240 # def __delete__(self, instance): 241 # try: 242 # return delattr(instance, self.cache_name) 243 # except AttributeError: 244 # pass 225 245 226 246 class ReverseSingleRelatedObjectDescriptor(object): 227 247 # This class provides the functionality that makes the related-object … … 625 645 626 646 class ManyToOneRel(object): 627 647 def __init__(self, to, field_name, related_name=None, 628 limit_choices_to=None, lookup_overrides=None, parent_link=False): 648 limit_choices_to=None, lookup_overrides=None, parent_link=False, 649 on_delete=None): 629 650 try: 630 651 to._meta 631 652 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 638 659 self.lookup_overrides = lookup_overrides or {} 639 660 self.multiple = True 640 661 self.parent_link = parent_link 662 self.on_delete = on_delete 641 663 642 664 def is_hidden(self): 643 665 "Should the related object be hidden?" … … 656 678 657 679 class OneToOneRel(ManyToOneRel): 658 680 def __init__(self, to, field_name, related_name=None, 659 limit_choices_to=None, lookup_overrides=None, parent_link=False): 681 limit_choices_to=None, lookup_overrides=None, parent_link=False, 682 on_delete=None): 660 683 super(OneToOneRel, self).__init__(to, field_name, 661 684 related_name=related_name, limit_choices_to=limit_choices_to, 662 lookup_overrides=lookup_overrides, parent_link=parent_link) 685 lookup_overrides=lookup_overrides, parent_link=parent_link, 686 on_delete=on_delete) 663 687 self.multiple = False 664 688 665 689 class ManyToManyRel(object): … … 705 729 related_name=kwargs.pop('related_name', None), 706 730 limit_choices_to=kwargs.pop('limit_choices_to', None), 707 731 lookup_overrides=kwargs.pop('lookup_overrides', None), 708 parent_link=kwargs.pop('parent_link', False)) 732 parent_link=kwargs.pop('parent_link', False), 733 on_delete=kwargs.pop('on_delete', None)) 709 734 Field.__init__(self, **kwargs) 710 735 711 736 self.db_index = True … … 750 775 target = self.rel.to._meta.db_table 751 776 cls._meta.duplicate_targets[self.column] = (target, "o2m") 752 777 778 on_delete = self.rel.on_delete 779 if on_delete not in ALLOWED_ON_DELETE_ACTION_TYPES: 780 raise ValueError("Invalid value 'on_delete=%s' specified for %s %s.%s." % (on_delete, type(self).__name__, cls.__name__, name)) 781 if on_delete == SET_NULL and not self.null: 782 specification = "'on_delete=SET_NULL'" 783 raise ValueError("%s specified for %s '%s.%s', but the field is not nullable." % (specification, type(self).__name__, cls.__name__, name)) 784 if on_delete == SET_DEFAULT and not self.has_default(): 785 specification = "'on_delete=SET_DEFAULT'" 786 raise ValueError("%s specified for %s '%s.%s', but the field has no default value." % (specification, type(self).__name__, cls.__name__, name)) 787 753 788 def contribute_to_related_class(self, cls, related): 754 789 # Internal FK's - i.e., those with a related name ending with '+' - 755 790 # don't get a related descriptor. -
django/db/models/__init__.py
11 11 from django.db.models.fields.subclassing import SubfieldBase 12 12 from django.db.models.fields.files import FileField, ImageField 13 13 from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel 14 from django.db.models.fields.related import CASCADE, PROTECT, SET_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 for cls, cls_dct in fields_to_set.iteritems(): 1027 #TODO: batch these, similar to UpdateQuery.clear_related? 1028 #(Note that it may be harder to do here because the default value 1029 #for a given field may be different for each instance, 1030 #while UpdateQuery.clear_related always uses the value None). 1031 query = sql.UpdateQuery(cls, connection) 1032 for instance, field_names_and_values in cls_dct.itervalues(): 1033 query.where = query.where_class() 1034 pk = query.model._meta.pk 1035 query.where.add((sql.where.Constraint(None, pk.column, pk), 'exact', instance.pk), sql.where.AND) 1036 query.add_update_values(field_names_and_values) 1037 query.execute_sql() 1038 1024 1039 for cls in ordered_classes: 1025 1040 items = seen_objs[cls].items() 1026 1041 items.sort() … … 1031 1046 if not cls._meta.auto_created: 1032 1047 signals.pre_delete.send(sender=cls, instance=instance) 1033 1048 1049 # Handle related GenericRelation and ManyToManyField instances 1034 1050 pk_list = [pk for pk,instance in items] 1035 1051 del_query = sql.DeleteQuery(cls, connection) 1036 1052 del_query.delete_batch_related(pk_list) 1037 1053 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 1054 for cls in ordered_classes: 1051 1055 items = obj_pairs[cls] 1052 1056 items.reverse() 1053 1054 1057 pk_list = [pk for pk,instance in items] 1055 1058 del_query = sql.DeleteQuery(cls, connection) 1056 1059 del_query.delete_batch(pk_list) 1057 1060 1058 # Last cleanup; set NULLs where there once was a reference to the 1059 # object, NULL the primary key of the found objects, and perform 1060 # post-notification. 1061 #Last cleanup; set NULLs and default values where there once was a 1062 #reference to the object, NULL the primary key of the found objects, 1063 #and perform post-notification. 1064 for cls, cls_dct in fields_to_set.iteritems(): 1065 for instance, field_names_and_values in cls_dct.itervalues(): 1066 for field_name, field_value in field_names_and_values.iteritems(): 1067 field = cls._meta.get_field_by_name(field_name)[0] 1068 setattr(instance, field.attname, field_value) 1069 for cls in ordered_classes: 1070 items = obj_pairs[cls] 1071 items.reverse() 1061 1072 for pk_val, instance in items: 1062 1073 for field in cls._meta.fields: 1063 1074 if field.rel and field.null and field.rel.to in seen_objs: -
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 name 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 self.data = {} 136 137 def add(self, model, pk, obj, field_name, field_value): 138 """ 139 Adds an item. 140 model is the class of the object being added, 141 pk is the primary key, obj is the object itself, 142 field_name is the name of the field to be set, 143 field_value is the value it needs to be set to. 144 """ 145 d = self.data.setdefault(model, SortedDict()) 146 obj, field_names_and_values = d.setdefault(pk, (obj, dict())) 147 assert field_name not in field_names_and_values or field_names_and_values[field_name] == field_value 148 field_names_and_values[field_name] = field_value 149 150 def __contains__(self, key): 151 return self.data.__contains__(key) 152 153 def __getitem__(self, key): 154 return self.data[key] 155 156 def __nonzero__(self): 157 return bool(self.data) 158 159 def iteritems(self): 160 return self.data.iteritems() 161 162 def iterkeys(self): 163 return self.data.iterkeys() 164 165 def itervalues(self): 166 return self.data.itervalues() 167 168 def items(self): 169 return self.data.items() 170 171 def keys(self): 172 return self.data.keys() 173 174 def values(self): 175 return self.data.values() 176 127 177 class QueryWrapper(object): 128 178 """ 129 179 A type that indicates the contents are an SQL fragment and the associate -
tests/modeltests/on_delete_django/tests.py
Property changes on: tests/modeltests/on_delete_django/__init__.py ___________________________________________________________________ Name: svn:keywords + Id
1 from django.conf import settings 2 from django.db import IntegrityError, models 3 from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT 4 from django.test import TestCase 5 6 from models import * 7 8 class ON_DELETE_Tests(TestCase): 9 ### ForeignKey tests 10 11 def test_ForeignKey_CASCADE(self): 12 self._CASCADE_test(ForeignKey_CASCADE, True) 13 14 def test_ForeignKey_PROTECT(self): 15 self._PROTECT_test(ForeignKey_PROTECT, True) 16 17 def test_ForeignKey_SET_NULL(self): 18 self._SET_NULL_test(ForeignKey_SET_NULL, True) 19 20 def test_ForeignKey_SET_DEFAULT(self): 21 self._SET_DEFAULT_test(ForeignKey_SET_DEFAULT, True) 22 23 def test_ForeignKey_None_Null_True(self): 24 #If no on_delete behavior is specified, 25 #Django treats it as SET_NULL if the field is nullable 26 self._SET_NULL_test(ForeignKey_None_Null_True, True) 27 28 def test_ForeignKey_None_Null_False(self): 29 #If no on_delete behavior is specified, 30 #Django treats it as CASCADE if it is not nullable. 31 self._CASCADE_test(ForeignKey_None_Null_False, True) 32 33 def test_ForeignKey_Combined(self): 34 self._combined_test(ForeignKey_CASCADE, ForeignKey_PROTECT, ForeignKey_SET_NULL, ForeignKey_SET_DEFAULT) 35 36 def test_ForeignKey_SET_NULL_requires_nullable_field(self): 37 try: 38 class Test(models.Model): 39 fk = models.ForeignKey(Data, to_field='data', on_delete=models.SET_NULL) 40 except ValueError, e: 41 self.assertEqual(str(e), "'on_delete=SET_NULL' specified for ForeignKey 'Test.fk', but the field is not nullable.") 42 else: 43 self.assertTrue(False, 'Expected exception not raised.') 44 45 def test_ForeignKey_SET_DEFAULT_requires_default_value(self): 46 try: 47 class Test(models.Model): 48 fk = models.ForeignKey(Data, to_field='data', on_delete=models.SET_DEFAULT) 49 except ValueError, e: 50 self.assertEqual(str(e), "'on_delete=SET_DEFAULT' specified for ForeignKey 'Test.fk', but the field has no default value.") 51 else: 52 self.assertTrue(False, 'Expected exception not raised.') 53 54 ### OneToOneField tests 55 56 def test_OneToOne_CASCADE(self): 57 self._CASCADE_test(OneToOne_CASCADE, False) 58 59 def test_OneToOne_PROTECT(self): 60 self._PROTECT_test(OneToOne_PROTECT, False) 61 62 def test_OneToOne_SET_NULL(self): 63 self._SET_NULL_test(OneToOne_SET_NULL, False) 64 65 def test_OneToOne_SET_DEFAULT(self): 66 self._SET_DEFAULT_test(OneToOne_SET_DEFAULT, False) 67 68 def test_OneToOne_None_Null_True(self): 69 #If no on_delete behavior is specified, 70 #Django treats it as SET_NULL if the field is nullable 71 self._SET_NULL_test(OneToOne_None_Null_True, False) 72 73 def test_OneToOne_None_Null_False(self): 74 #If no on_delete behavior is specified, 75 #Django treats it as CASCADE if it is not nullable. 76 self._CASCADE_test(OneToOne_None_Null_False, False) 77 78 def test_OneToOne_Combined(self): 79 self._combined_test(OneToOne_CASCADE, OneToOne_PROTECT, OneToOne_SET_NULL, OneToOne_SET_DEFAULT) 80 81 def test_OneToOne_SET_NULL_requires_nullable_field(self): 82 try: 83 class Test(models.Model): 84 fk = models.OneToOneField(Data, to_field='data', on_delete=models.SET_NULL) 85 except ValueError, e: 86 self.assertEqual(str(e), "'on_delete=SET_NULL' specified for OneToOneField 'Test.fk', but the field is not nullable.") 87 else: 88 self.assertTrue(False, 'Expected exception not raised.') 89 90 def test_OneToOne_SET_DEFAULT_requires_default_value(self): 91 try: 92 class Test(models.Model): 93 fk = models.OneToOneField(Data, to_field='data', on_delete=models.SET_DEFAULT) 94 except ValueError, e: 95 self.assertEqual(str(e), "'on_delete=SET_DEFAULT' specified for OneToOneField 'Test.fk', but the field has no default value.") 96 else: 97 self.assertTrue(False, 'Expected exception not raised.') 98 99 ### Multiple FK tests 100 101 def test_All_FK_Model_1(self): 102 data = Data.objects.create(data=1) 103 fk = All_FK_Model.objects.create( 104 fk_ForeignKey_Cascade=data, 105 fk_ForeignKey_Protect=data, 106 fk_ForeignKey_Set_Null=data, 107 fk_ForeignKey_Set_Default=data, 108 fk_OneToOneField_Cascade=data, 109 fk_OneToOneField_Protect=data, 110 fk_OneToOneField_Set_Null=data, 111 fk_OneToOneField_Set_Default=data, 112 ) 113 fk_id = fk.pk 114 self.assertEqual(data, fk.fk_ForeignKey_Cascade) 115 self.assertEqual(data, fk.fk_ForeignKey_Protect) 116 self.assertEqual(data, fk.fk_ForeignKey_Set_Null) 117 self.assertEqual(data, fk.fk_ForeignKey_Set_Default) 118 self.assertEqual(data, fk.fk_OneToOneField_Cascade) 119 self.assertEqual(data, fk.fk_OneToOneField_Protect) 120 self.assertEqual(data, fk.fk_OneToOneField_Set_Null) 121 self.assertEqual(data, fk.fk_OneToOneField_Set_Default) 122 self.assertRaises(IntegrityError, data.delete) 123 124 fk.fk_ForeignKey_Protect = None 125 fk.fk_OneToOneField_Protect = data 126 fk.save() 127 self.assertRaises(IntegrityError, data.delete) 128 129 fk.fk_ForeignKey_Protect = data 130 fk.fk_OneToOneField_Protect = None 131 fk.save() 132 self.assertRaises(IntegrityError, data.delete) 133 134 fk.fk_ForeignKey_Protect = None 135 fk.fk_OneToOneField_Protect = None 136 fk.save() 137 data.delete() 138 self.assertRaises(All_FK_Model.DoesNotExist, All_FK_Model.objects.get, id=fk_id) 139 140 def test_All_FK_Model_2(self): 141 default_value = default_data() 142 143 data = Data.objects.create(data=1) 144 fk = All_FK_Model.objects.create( 145 fk_ForeignKey_Cascade=None, 146 fk_ForeignKey_Protect=None, 147 fk_ForeignKey_Set_Null=data, 148 fk_ForeignKey_Set_Default=data, 149 fk_OneToOneField_Cascade=None, 150 fk_OneToOneField_Protect=None, 151 fk_OneToOneField_Set_Null=data, 152 fk_OneToOneField_Set_Default=data, 153 ) 154 fk_id = fk.pk 155 self.assertEqual(None, fk.fk_ForeignKey_Cascade) 156 self.assertEqual(None, fk.fk_ForeignKey_Protect) 157 self.assertEqual(data, fk.fk_ForeignKey_Set_Null) 158 self.assertEqual(data, fk.fk_ForeignKey_Set_Default) 159 self.assertEqual(None, fk.fk_OneToOneField_Cascade) 160 self.assertEqual(None, fk.fk_OneToOneField_Protect) 161 self.assertEqual(data, fk.fk_OneToOneField_Set_Null) 162 self.assertEqual(data, fk.fk_OneToOneField_Set_Default) 163 164 data.delete() 165 166 fk = All_FK_Model.objects.get(pk=fk_id) #TODO: fix bug that makes this necessary 167 self.assertEqual(None, fk.fk_ForeignKey_Cascade) 168 self.assertEqual(None, fk.fk_ForeignKey_Protect) 169 self.assertEqual(None, fk.fk_ForeignKey_Set_Null) 170 self.assertEqual(default_value, fk.fk_ForeignKey_Set_Default) 171 self.assertEqual(None, fk.fk_OneToOneField_Cascade) 172 self.assertEqual(None, fk.fk_OneToOneField_Protect) 173 self.assertEqual(None, fk.fk_OneToOneField_Set_Null) 174 self.assertEqual(default_value, fk.fk_OneToOneField_Set_Default) 175 176 #TODO: not done yet: 177 # def test_All_FK_Model_3(self): 178 # default_value = default_data() 179 # 180 # data = Data.objects.create(data=1) 181 # fk1 = All_FK_Model.objects.create( 182 # fk_ForeignKey_Cascade=None, 183 # fk_ForeignKey_Protect=None, 184 # fk_ForeignKey_Set_Null=data, 185 # fk_ForeignKey_Set_Default=data, 186 # fk_OneToOneField_Cascade=None, 187 # fk_OneToOneField_Protect=None, 188 # fk_OneToOneField_Set_Null=data, 189 # fk_OneToOneField_Set_Default=data, 190 # ) 191 # fk2 = All_FK_Model.objects.create( 192 # fk_ForeignKey_Cascade=None, 193 # fk_ForeignKey_Protect=None, 194 # fk_ForeignKey_Set_Null=data, 195 # fk_ForeignKey_Set_Default=data, 196 # fk_OneToOneField_Cascade=None, 197 # fk_OneToOneField_Protect=None, 198 # fk_OneToOneField_Set_Null=None, 199 # fk_OneToOneField_Set_Default=None, 200 # ) 201 # fk1_id = fk1.pk 202 # fk2_id = fk2.pk 203 # self.assertEqual(None, fk1.fk_ForeignKey_Cascade) 204 # self.assertEqual(None, fk1.fk_ForeignKey_Protect) 205 # self.assertEqual(data, fk1.fk_ForeignKey_Set_Null) 206 # self.assertEqual(data, fk1.fk_ForeignKey_Set_Default) 207 # self.assertEqual(None, fk1.fk_OneToOneField_Cascade) 208 # self.assertEqual(None, fk1.fk_OneToOneField_Protect) 209 # self.assertEqual(data, fk1.fk_OneToOneField_Set_Null) 210 # self.assertEqual(data, fk1.fk_OneToOneField_Set_Default) 211 # self.assertEqual(None, fk2.fk_ForeignKey_Cascade) 212 # self.assertEqual(None, fk2.fk_ForeignKey_Protect) 213 # self.assertEqual(data, fk2.fk_ForeignKey_Set_Null) 214 # self.assertEqual(data, fk2.fk_ForeignKey_Set_Default) 215 # self.assertEqual(None, fk2.fk_OneToOneField_Cascade) 216 # self.assertEqual(None, fk2.fk_OneToOneField_Protect) 217 # self.assertEqual(None, fk2.fk_OneToOneField_Set_Null) 218 # self.assertEqual(None, fk2.fk_OneToOneField_Set_Default) 219 # 220 # data.delete() 221 # 222 # fk1 = All_FK_Model.objects.get(pk=fk1_id) #TODO: fix bug that makes this necessary 223 # fk2 = All_FK_Model.objects.get(pk=fk2_id) #TODO: fix bug that makes this necessary 224 # self.assertEqual(None, fk1.fk_ForeignKey_Cascade) 225 # self.assertEqual(None, fk1.fk_ForeignKey_Protect) 226 # self.assertEqual(None, fk1.fk_ForeignKey_Set_Null) 227 # self.assertEqual(default_value, fk1.fk_ForeignKey_Set_Default) 228 # self.assertEqual(None, fk1.fk_OneToOneField_Cascade) 229 # self.assertEqual(None, fk1.fk_OneToOneField_Protect) 230 # self.assertEqual(None, fk1.fk_OneToOneField_Set_Null) 231 # self.assertEqual(default_value, fk1.fk_OneToOneField_Set_Default) 232 # self.assertEqual(None, fk2.fk_ForeignKey_Cascade) 233 # self.assertEqual(None, fk2.fk_ForeignKey_Protect) 234 # self.assertEqual(None, fk2.fk_ForeignKey_Set_Null) 235 # self.assertEqual(default_value, fk2.fk_ForeignKey_Set_Default) 236 # self.assertEqual(None, fk2.fk_OneToOneField_Cascade) 237 # self.assertEqual(None, fk2.fk_OneToOneField_Protect) 238 # self.assertEqual(None, fk2.fk_OneToOneField_Set_Null) 239 # self.assertEqual(default_value, fk2.fk_OneToOneField_Set_Default) 240 241 ### Cache tests 242 243 #These tests illustrate a caching problem with OneToOneKeyField. 244 def test_issue_1a(self): 245 data = Data.objects.create(data=1) 246 fk = OneToOne_PROTECT.objects.create(fk=data) 247 fk.delete() 248 data.delete() #This succeeds 249 def test_issue_1b(self): 250 data = Data.objects.create(data=1) 251 fk = OneToOne_PROTECT.objects.create(fk=data) 252 self.assertRaises(IntegrityError, data.delete) #This line added 253 #This loads the item into the SingleRelatedObjectDescriptor cache; 254 #this cached item is not unloaded after the fk.delete(), 255 #which later causes the data.delete() to fail. 256 fk.delete() 257 data.delete() #This fails 258 def test_issue_1c(self): 259 data = Data.objects.create(data=1) 260 fk = ForeignKey_PROTECT.objects.create(fk=data) #This line changed 261 #This problem applies only to OneToOneField; 262 #ForeignKey doesn't have the same problem 263 self.assertRaises(IntegrityError, data.delete) 264 fk.delete() 265 data.delete() #This succeeds 266 def test_issue_1d(self): 267 data = Data.objects.create(data=1) 268 fk = OneToOne_PROTECT.objects.create(fk=data) 269 self.assertRaises(IntegrityError, data.delete) 270 fk = OneToOne_PROTECT.objects.get(pk=fk.pk) 271 fk.delete() 272 data.delete() #This fails 273 def test_issue_1e(self): 274 data = Data.objects.create(data=1) 275 fk = OneToOne_PROTECT.objects.create(fk=data) 276 self.assertRaises(IntegrityError, data.delete) 277 fk.delete() 278 data = Data.objects.get(pk=data.pk) 279 data.delete() #This succeeds 280 #Refreshing the data object unloads/refreshes its cache 281 282 #These tests illustrate a problem with both ForeignKey and OneToOneKeyField. 283 #The database is changed correctly, but instances in memory are not updated correctly. 284 #The problem is that, although some instances are changed in memory, other instances 285 #referring to the same database record are not, and these become out of sync 286 #with the db data. If these out of sync instances are then used, issues occur. 287 def test_issue_2a(self): 288 default_value = default_data() 289 data = Data.objects.create(data=1) 290 fk = ForeignKey_SET_DEFAULT.objects.create(fk=data) 291 data.delete() 292 self.assertEqual(default_value, fk.fk) #Fails 293 def test_issue_2b(self): 294 data = Data.objects.create(data=1) 295 fk = ForeignKey_SET_NULL.objects.create(fk=data) 296 data.delete() 297 self.assertEqual(None, fk.fk) #Fails 298 def test_issue_2c(self): 299 data = Data.objects.create(data=1) 300 fk = ForeignKey_SET_NULL.objects.create(fk=data) 301 data.delete() 302 fk = ForeignKey_SET_NULL.objects.get(pk=fk.pk) #Added this line 303 self.assertEqual(None, fk.fk) #Succeeds 304 def test_issue_2d(self): 305 default_value = default_data() 306 data = Data.objects.create(data=1) 307 fk = OneToOne_SET_DEFAULT.objects.create(fk=data) 308 data.delete() 309 self.assertEqual(default_value, fk.fk) #Fails 310 def test_issue_2e(self): 311 data = Data.objects.create(data=1) 312 fk = OneToOne_SET_NULL.objects.create(fk=data) 313 data.delete() 314 self.assertEqual(None, fk.fk) #Fails 315 def test_issue_2f(self): 316 data = Data.objects.create(data=1) 317 fk = OneToOne_SET_NULL.objects.create(fk=data) 318 data.delete() 319 fk = OneToOne_SET_NULL.objects.get(pk=fk.pk) #Added this line 320 self.assertEqual(None, fk.fk) #Succeeds 321 322 ### Utility methods 323 324 def _CASCADE_test(self, foreign_key_model, many_to_one): 325 #Create a data item and foreign key(s) to it 326 data, fk_id_1 = self._create_data_item_and_foreign_key(1, foreign_key_model) 327 if many_to_one: 328 _, fk_id_2 = self._create_data_item_and_foreign_key(1, foreign_key_model) 329 330 #Create an unrelated data item and foreign key 331 _, fk_id_unlrelated = self._create_data_item_and_foreign_key(2, foreign_key_model) 332 333 #Delete the data item and make sure the foreign key is affected appropriately 334 data.delete() 335 self.assertEqual(None, data.pk) 336 self.assertRaises(foreign_key_model.DoesNotExist, foreign_key_model.objects.get, id=fk_id_1) 337 if many_to_one: 338 self.assertRaises(foreign_key_model.DoesNotExist, foreign_key_model.objects.get, id=fk_id_2) 339 340 #Make sure the unrelated data item and foreign key are unaffected 341 self._test_data_item_and_foreign_key(None, 2, foreign_key_model, fk_id_unlrelated) 342 343 def _PROTECT_test(self, foreign_key_model, many_to_one): 344 #Create a data item and foreign key(s) to it 345 data, fk_id_1 = self._create_data_item_and_foreign_key(1, foreign_key_model) 346 if many_to_one: 347 _, fk_id_2 = self._create_data_item_and_foreign_key(1, foreign_key_model) 348 349 #Create an unrelated data item and foreign key 350 data_unrelated, fk_id_unlrelated = self._create_data_item_and_foreign_key(2, foreign_key_model) 351 352 #Delete the data item and make sure the foreign key is affected appropriately 353 self.assertRaises(IntegrityError, data.delete) 354 self.assertNotEqual(None, data.pk) 355 self._test_data_item_and_foreign_key(data, 1, foreign_key_model, fk_id_1) 356 if many_to_one: 357 self._test_data_item_and_foreign_key(None, 1, foreign_key_model, fk_id_2) 358 359 #Make sure the unrelated data item and foreign key are unaffected 360 self._test_data_item_and_foreign_key(data_unrelated, 2, foreign_key_model, fk_id_unlrelated) 361 362 def _SET_NULL_test(self, foreign_key_model, many_to_one): 363 #Create a data item and foreign key(s) to it 364 data, fk_id_1 = self._create_data_item_and_foreign_key(1, foreign_key_model) 365 if many_to_one: 366 _, fk_id_2 = self._create_data_item_and_foreign_key(1, foreign_key_model) 367 368 #Create an unrelated data item and foreign key 369 data_unrelated, fk_id_unlrelated = self._create_data_item_and_foreign_key(2, foreign_key_model) 370 371 #Delete the data item and make sure the foreign key is affected appropriately 372 data.delete() 373 self.assertEqual(None, data.pk) 374 self.assertEqual(None, foreign_key_model.objects.get(id=fk_id_1).fk) 375 self.assertEqual(None, foreign_key_model.objects.get(id=fk_id_1).fk_id) 376 if many_to_one: 377 self.assertEqual(None, foreign_key_model.objects.get(id=fk_id_2).fk) 378 self.assertEqual(None, foreign_key_model.objects.get(id=fk_id_2).fk_id) 379 380 #Make sure the unrelated data item and foreign key are unaffected 381 self._test_data_item_and_foreign_key(data_unrelated, 2, foreign_key_model, fk_id_unlrelated) 382 383 def _SET_DEFAULT_test(self, foreign_key_model, many_to_one): 384 #Create a data item and foreign key(s) to it 385 data, fk_id_1 = self._create_data_item_and_foreign_key(1, foreign_key_model) 386 if many_to_one: 387 _, fk_id_2 = self._create_data_item_and_foreign_key(1, foreign_key_model) 388 389 #Create an unrelated data item and foreign key 390 data_unrelated, fk_id_unlrelated = self._create_data_item_and_foreign_key(2, foreign_key_model) 391 392 #Delete the data item and make sure the foreign key is affected appropriately 393 data.delete() 394 self.assertEqual(None, data.pk) 395 default_value = default_data() 396 self.assertEqual(default_value, foreign_key_model.objects.get(id=fk_id_1).fk) 397 if many_to_one: 398 self.assertEqual(default_value, foreign_key_model.objects.get(id=fk_id_2).fk) 399 400 #Make sure the unrelated data item and foreign key are unaffected 401 self._test_data_item_and_foreign_key(data_unrelated, 2, foreign_key_model, fk_id_unlrelated) 402 403 def _combined_test(self, cascade_model, protect_model, set_null_model, set_default_model): 404 #Create data items and foreign key 405 data, fk_id_cascade = self._create_data_item_and_foreign_key(1, cascade_model) 406 _, fk_id_protect = self._create_data_item_and_foreign_key(1, protect_model) 407 _, fk_id_set_null = self._create_data_item_and_foreign_key(1, set_null_model) 408 _, fk_id_set_default = self._create_data_item_and_foreign_key(1, set_default_model) 409 data_id = data.pk 410 411 #Attempt to delete--should do nothing due to PROTECT foreign key 412 self.assertRaises(IntegrityError, data.delete) 413 self.assertNotEqual(None, data.pk) 414 self._test_data_item_and_foreign_key(data, 1, cascade_model, fk_id_cascade) 415 self._test_data_item_and_foreign_key(data, 1, protect_model, fk_id_protect) 416 self._test_data_item_and_foreign_key(data, 1, set_null_model, fk_id_set_null) 417 self._test_data_item_and_foreign_key(data, 1, set_default_model, fk_id_set_default) 418 419 #Delete protect foreign key and try again--this time, it should work 420 fk = protect_model.objects.get(id=fk_id_protect) 421 fk.delete() 422 data = Data.objects.get(pk=data_id) 423 data.delete() 424 self.assertEqual(None, data.pk) 425 self.assertRaises(cascade_model.DoesNotExist, cascade_model.objects.get, id=fk_id_cascade) 426 self.assertEqual(None, set_null_model.objects.get(id=fk_id_set_null).fk) 427 self.assertEqual(None, set_null_model.objects.get(id=fk_id_set_null).fk_id) 428 self.assertEqual(default_data(), set_default_model.objects.get(id=fk_id_set_default).fk) 429 430 def _create_data_item_and_foreign_key(self, data_value, fk_model): 431 data, _ = Data.objects.get_or_create(data=data_value) 432 fk = fk_model.objects.create(fk=data) 433 self._test_data_item_and_foreign_key(data, data_value, fk_model, fk.id) 434 return data, fk.id 435 436 def _test_data_item_and_foreign_key(self, data_item, data_value, fk_model, fk_id): 437 data = Data.objects.get(data=data_value) 438 if data_item: 439 self.assertEqual(data.pk, data_item.pk) 440 self.assertEqual(data.data, data_item.data) 441 fk = fk_model.objects.get(id=fk_id) 442 self.assertEqual(data, fk.fk) 443 No newline at end of file -
tests/modeltests/on_delete_django/models.py
Property changes on: tests/modeltests/on_delete_django/tests.py ___________________________________________________________________ Name: svn:keywords + Id
1 """ 2 Test ON DELETE behavior for foreign keys 3 when it's handled by the Django. 4 """ 5 6 from django.db import models 7 8 ### Data model 9 10 class Data(models.Model): 11 data = models.IntegerField(unique=True) 12 13 def default_data(): 14 data, _ = Data.objects.get_or_create(data=1000) 15 return data 16 17 ### ForeignKey models 18 19 class ForeignKey_CASCADE(models.Model): 20 fk = models.ForeignKey(Data, on_delete=models.CASCADE) 21 22 class ForeignKey_PROTECT(models.Model): 23 fk = models.ForeignKey(Data, on_delete=models.PROTECT) 24 25 class ForeignKey_SET_NULL(models.Model): 26 fk = models.ForeignKey(Data, null=True, on_delete=models.SET_NULL) 27 28 class ForeignKey_SET_DEFAULT(models.Model): 29 fk = models.ForeignKey(Data, default=default_data, on_delete=models.SET_DEFAULT) 30 31 class ForeignKey_None_Null_True(models.Model): 32 fk = models.ForeignKey(Data, null=True, on_delete=None) 33 34 class ForeignKey_None_Null_False(models.Model): 35 fk = models.ForeignKey(Data, on_delete=None) 36 37 ### OneToOneField models 38 39 class OneToOne_CASCADE(models.Model): 40 fk = models.OneToOneField(Data, on_delete=models.CASCADE) 41 42 class OneToOne_PROTECT(models.Model): 43 fk = models.OneToOneField(Data, on_delete=models.PROTECT) 44 45 class OneToOne_SET_NULL(models.Model): 46 fk = models.OneToOneField(Data, null=True, on_delete=models.SET_NULL) 47 48 class OneToOne_SET_DEFAULT(models.Model): 49 fk = models.OneToOneField(Data, default=default_data, on_delete=models.SET_DEFAULT) 50 51 class OneToOne_None_Null_True(models.Model): 52 fk = models.OneToOneField(Data, null=True, on_delete=None) 53 54 class OneToOne_None_Null_False(models.Model): 55 fk = models.OneToOneField(Data, on_delete=None) 56 57 ### Multiple FK models 58 59 class All_FK_Model(models.Model): 60 fk_ForeignKey_Cascade = models.ForeignKey(Data, null=True, on_delete=models.CASCADE, related_name='fk1') 61 fk_ForeignKey_Protect = models.ForeignKey(Data, null=True, on_delete=models.PROTECT, related_name='fk2') 62 fk_ForeignKey_Set_Null = models.ForeignKey(Data, null=True, on_delete=models.SET_NULL, related_name='fk3') 63 fk_ForeignKey_Set_Default = models.ForeignKey(Data, null=True, default=default_data, on_delete=models.SET_DEFAULT, related_name='fk4') 64 fk_OneToOneField_Cascade = models.OneToOneField(Data, null=True, on_delete=models.CASCADE, related_name='fk5') 65 fk_OneToOneField_Protect = models.OneToOneField(Data, null=True, on_delete=models.PROTECT, related_name='fk6') 66 fk_OneToOneField_Set_Null = models.OneToOneField(Data, null=True, on_delete=models.SET_NULL, related_name='fk7') 67 fk_OneToOneField_Set_Default = models.OneToOneField(Data, null=True, default=default_data, on_delete=models.SET_DEFAULT, related_name='fk8') 68 No newline at end of file -
tests/modeltests/delete/models.py
Property changes on: tests/modeltests/on_delete_django/models.py ___________________________________________________________________ Name: svn:keywords + Id
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 }