Ticket #7539: on_delete_on_update.diff
File on_delete_on_update.diff, 39.2 KB (added by , 16 years ago) |
---|
-
django/db/models/base.py
12 12 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 13 13 from django.db.models.fields import AutoField, FieldDoesNotExist 14 14 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 15 from django.db.models.query import delete_objects, Q, Collected Objects15 from django.db.models.query import delete_objects, Q, CollectedFields, CollectedObjects 16 16 from django.db.models.options import Options 17 from django.db import connection, transaction, DatabaseError 17 from django.db import connection, transaction, DatabaseError, IntegrityError 18 18 from django.db.models import signals 19 from django.db.models.fields.related import CASCADE, RESTRICT, SET_NULL 19 20 from django.db.models.loading import register_models, get_model 20 21 from django.utils.functional import curry 21 22 from django.utils.encoding import smart_str, force_unicode, smart_unicode … … 412 413 413 414 save_base.alters_data = True 414 415 415 def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):416 def _collect_sub_objects(self, seen_objs, fields_to_null, parent=None, nullable=False): 416 417 """ 417 418 Recursively populates seen_objs with all objects related to this 418 419 object. … … 424 425 pk_val = self._get_pk_val() 425 426 if seen_objs.add(self.__class__, pk_val, self, parent, nullable): 426 427 return 428 429 if not getattr(settings, 'ON_DELETE_HANDLED_BY_DB', False): 430 ON_DELETE_NONE_HANDLING = getattr(settings, 'ON_DELETE_NONE_HANDLING', CASCADE) 427 431 428 for related in self._meta.get_all_related_objects(): 429 rel_opts_name = related.get_accessor_name() 430 if isinstance(related.field.rel, OneToOneRel): 431 try: 432 sub_obj = getattr(self, rel_opts_name) 433 except ObjectDoesNotExist: 434 pass 432 def _handle_sub_obj(related, sub_obj): 433 on_delete = related.field.rel.on_delete 434 if on_delete is None: 435 on_delete = ON_DELETE_NONE_HANDLING 436 437 if on_delete == CASCADE: 438 sub_obj._collect_sub_objects(seen_objs, fields_to_null, self.__class__) 439 elif on_delete == RESTRICT: 440 msg = '[Django] Cannot delete a parent object: a foreign key constraint fails (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 441 sub_obj.__class__, 442 sub_obj._get_pk_val(), 443 self.__class__, 444 pk_val, 445 ) 446 raise IntegrityError(msg) #TODO: include error number also? E.g., "raise IntegrityError(1451, msg)" (but 1451 is MySQL-specific, I believe) 447 elif on_delete == SET_NULL: 448 if not related.field.null: 449 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`))' % ( 450 sub_obj.__class__, 451 sub_obj._get_pk_val(), 452 self.__class__, 453 pk_val, 454 ) 455 raise IntegrityError(msg) #TODO: include error number also? E.g., "raise IntegrityError(<errno>, msg)" (but the error numbers are db-specific, I believe) 456 fields_to_null.add(sub_obj.__class__, sub_obj._get_pk_val(), sub_obj, related.field.name) 435 457 else: 436 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null) 437 else: 438 for sub_obj in getattr(self, rel_opts_name).all(): 439 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null) 458 raise AttributeError('Unexpected value for on_delete') 440 459 441 # Handle any ancestors (for the model-inheritance case). We do this by 442 # traversing to the most remote parent classes -- those with no parents 443 # themselves -- and then adding those instances to the collection. That 444 # will include all the child instances down to "self". 445 parent_stack = self._meta.parents.values() 446 while parent_stack: 447 link = parent_stack.pop() 448 parent_obj = getattr(self, link.name) 449 if parent_obj._meta.parents: 450 parent_stack.extend(parent_obj._meta.parents.values()) 451 continue 452 # At this point, parent_obj is base class (no ancestor models). So 453 # delete it and all its descendents. 454 parent_obj._collect_sub_objects(seen_objs) 460 for related in self._meta.get_all_related_objects(): 461 rel_opts_name = related.get_accessor_name() 462 if isinstance(related.field.rel, OneToOneRel): 463 try: 464 sub_obj = getattr(self, rel_opts_name) 465 except ObjectDoesNotExist: 466 pass 467 else: 468 _handle_sub_obj(related, sub_obj) 469 else: 470 for sub_obj in getattr(self, rel_opts_name).all(): 471 _handle_sub_obj(related, sub_obj) 455 472 473 # Handle any ancestors (for the model-inheritance case). We do this by 474 # traversing to the most remote parent classes -- those with no parents 475 # themselves -- and then adding those instances to the collection. That 476 # will include all the child instances down to "self". 477 parent_stack = self._meta.parents.values() 478 while parent_stack: 479 link = parent_stack.pop() 480 parent_obj = getattr(self, link.name) 481 if parent_obj._meta.parents: 482 parent_stack.extend(parent_obj._meta.parents.values()) 483 continue 484 # At this point, parent_obj is base class (no ancestor models). So 485 # delete it and all its descendents. 486 parent_obj._collect_sub_objects(seen_objs, fields_to_null) 487 456 488 def delete(self): 457 489 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) 458 490 459 491 # Find all the objects than need to be deleted. 460 492 seen_objs = CollectedObjects() 461 self._collect_sub_objects(seen_objs) 493 fields_to_null = CollectedFields() 494 self._collect_sub_objects(seen_objs, fields_to_null) 462 495 463 496 # Actually delete the objects. 464 delete_objects(seen_objs )497 delete_objects(seen_objs, fields_to_null) 465 498 466 499 delete.alters_data = True 467 500 -
django/db/models/fields/related.py
10 10 from django.utils.functional import curry 11 11 from django.core import exceptions 12 12 from django import forms 13 from django.conf import settings 13 14 14 15 try: 15 16 set … … 20 21 21 22 pending_lookups = {} 22 23 24 class _OnDeleteOrUpdateAction(object): 25 pass 26 class RESTRICT(_OnDeleteOrUpdateAction): 27 sql = 'RESTRICT' 28 class CASCADE(_OnDeleteOrUpdateAction): 29 sql = 'CASCADE' 30 class SET_NULL(_OnDeleteOrUpdateAction): 31 sql = 'SET NULL' 32 ALLOWED_ON_DELETE_ACTION_TYPES = set([None, CASCADE, RESTRICT, SET_NULL]) 33 ALLOWED_ON_UPDATE_ACTION_TYPES = ALLOWED_ON_DELETE_ACTION_TYPES.copy() 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, … … 584 596 585 597 class ManyToOneRel(object): 586 598 def __init__(self, to, field_name, related_name=None, 587 limit_choices_to=None, lookup_overrides=None, parent_link=False): 599 limit_choices_to=None, lookup_overrides=None, parent_link=False, 600 on_delete=None, on_update=None): 588 601 try: 589 602 to._meta 590 603 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT … … 597 610 self.lookup_overrides = lookup_overrides or {} 598 611 self.multiple = True 599 612 self.parent_link = parent_link 613 self.on_delete = on_delete 614 self.on_update = on_update 600 615 601 616 def get_related_field(self): 602 617 """ … … 611 626 612 627 class OneToOneRel(ManyToOneRel): 613 628 def __init__(self, to, field_name, related_name=None, 614 limit_choices_to=None, lookup_overrides=None, parent_link=False): 629 limit_choices_to=None, lookup_overrides=None, parent_link=False, 630 on_delete=None, on_update=None): 615 631 super(OneToOneRel, self).__init__(to, field_name, 616 632 related_name=related_name, limit_choices_to=limit_choices_to, 617 lookup_overrides=lookup_overrides, parent_link=parent_link) 633 lookup_overrides=lookup_overrides, parent_link=parent_link, 634 on_delete=on_delete, on_update=on_update) 618 635 self.multiple = False 619 636 620 637 class ManyToManyRel(object): … … 645 662 related_name=kwargs.pop('related_name', None), 646 663 limit_choices_to=kwargs.pop('limit_choices_to', None), 647 664 lookup_overrides=kwargs.pop('lookup_overrides', None), 648 parent_link=kwargs.pop('parent_link', False)) 665 parent_link=kwargs.pop('parent_link', False), 666 on_delete=kwargs.pop('on_delete', None), 667 on_update=kwargs.pop('on_update', None)) 649 668 Field.__init__(self, **kwargs) 650 669 651 670 self.db_index = True … … 690 709 target = self.rel.to._meta.db_table 691 710 cls._meta.duplicate_targets[self.column] = (target, "o2m") 692 711 712 on_delete, on_update = self.rel.on_delete, self.rel.on_update 713 if on_delete not in ALLOWED_ON_DELETE_ACTION_TYPES: 714 raise ValueError("Invalid value 'on_delete=%s' specified for ForeignKey field %s.%s." % (on_delete, cls.__name__, name)) 715 if on_update not in ALLOWED_ON_UPDATE_ACTION_TYPES: 716 raise ValueError("Invalid value 'on_update=%s' specified for ForeignKey field %s.%s." % (on_update, cls.__name__, name)) 717 if (on_delete == SET_NULL or on_update == SET_NULL) and not self.null: 718 if on_delete == SET_NULL and on_update == SET_NULL: 719 specification = "'on_delete=SET_NULL' and 'on_update=SET_NULL'" 720 elif on_delete == SET_NULL: 721 specification = "'on_delete=SET_NULL'" 722 else: 723 specification = "'on_update=SET_NULL'" 724 raise ValueError("%s specified for ForeignKey field '%s.%s', but the field is not nullable." % (specification, cls.__name__, name)) 725 if on_delete is None and getattr(settings, 'ON_DELETE_NONE_HANDLING', CASCADE) == SET_NULL and not self.null: 726 raise ValueError("on_delete=SET_NULL is being used by default (based on the ON_DELETE_NONE_HANDLING setting) for ForeignKey field '%s.%s', but the field is not nullable." % (specification, cls.__name__, name)) 727 693 728 def contribute_to_related_class(self, cls, related): 694 729 setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 695 730 -
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, RESTRICT, SET_NULL 14 15 from django.db.models import signals 15 16 16 17 # Admin stages. -
django/db/models/query.py
117 117 """ 118 118 return self.data.keys() 119 119 120 class CollectedFields(object): 121 """ 122 A container that stores model objects and fields that need to 123 be nulled to enforce the on_delete=SET_NULL ForeignKey constraint. 124 """ 120 125 126 def __init__(self): 127 self.data = {} 128 129 def add(self, model, pk, obj, field_name): 130 """ 131 Adds an item. 132 model is the class of the object being added, 133 pk is the primary key, obj is the object itself, 134 field_name is the name of the field to be nulled. 135 """ 136 d = self.data.setdefault(model, SortedDict()) 137 obj, field_names = d.setdefault(pk, (obj, set())) 138 field_names.add(field_name) 139 140 def __contains__(self, key): 141 return self.data.__contains__(key) 142 143 def __getitem__(self, key): 144 return self.data[key] 145 146 def __nonzero__(self): 147 return bool(self.data) 148 149 def iteritems(self): 150 return self.data.iteritems() 151 152 def iterkeys(self): 153 return self.data.iterkeys() 154 155 def itervalues(self): 156 return self.data.itervalues() 157 121 158 class QuerySet(object): 122 159 """ 123 160 Represents a lazy database lookup for a set of objects. … … 427 464 # Collect all the objects to be deleted in this chunk, and all the 428 465 # objects that are related to the objects that are to be deleted. 429 466 seen_objs = CollectedObjects() 467 fields_to_null = CollectedFields() 430 468 for object in del_query[:CHUNK_SIZE]: 431 object._collect_sub_objects(seen_objs )469 object._collect_sub_objects(seen_objs, fields_to_null) 432 470 433 471 if not seen_objs: 434 472 break 435 delete_objects(seen_objs )473 delete_objects(seen_objs, fields_to_null) 436 474 437 475 # Clear the result cache, in case this QuerySet gets reused. 438 476 self._result_cache = None … … 951 989 setattr(obj, f.get_cache_name(), rel_obj) 952 990 return obj, index_end 953 991 954 955 def delete_objects(seen_objs): 992 def delete_objects(seen_objs, fields_to_null): 956 993 """ 957 994 Iterate through a list of seen classes, and remove any instances that are 958 995 referred to. … … 992 1029 update_query.clear_related(field, pk_list) 993 1030 994 1031 # Now delete the actual data. 1032 for cls, cls_dct in fields_to_null.iteritems(): 1033 update_query = sql.UpdateQuery(cls, connection) 1034 field_dict = {} 1035 for pk, (_, field_names) in cls_dct.iteritems(): 1036 for field_name in field_names: 1037 pk_set = field_dict.setdefault(field_name, set()) 1038 pk_set.add(pk) 1039 for field_name, pk_set in field_dict.iteritems(): 1040 update_query.clear_related(cls._meta.get_field_by_name(field_name)[0], list(pk_set)) 995 1041 for cls in ordered_classes: 996 1042 items = obj_pairs[cls] 997 1043 items.reverse() … … 1003 1049 # Last cleanup; set NULLs where there once was a reference to the 1004 1050 # object, NULL the primary key of the found objects, and perform 1005 1051 # post-notification. 1052 for cls, cls_dct in fields_to_null.iteritems(): 1053 for instance, field_names in cls_dct.itervalues(): 1054 for field_name in field_names: 1055 field = cls._meta.get_field_by_name(field_name)[0] 1056 setattr(instance, field.attname, None) 1006 1057 for pk_val, instance in items: 1007 1058 for field in cls._meta.fields: 1008 1059 if field.rel and field.null and field.rel.to in seen_objs: -
django/db/backends/creation.py
94 94 "Return the SQL snippet defining the foreign key reference for a field" 95 95 qn = self.connection.ops.quote_name 96 96 if field.rel.to in known_models: 97 on_delete_clause, on_update_clause = self.on_delete_and_update_clauses(style, field) 97 98 output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \ 98 99 style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \ 99 100 style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' + 101 on_delete_clause + on_update_clause + 100 102 self.connection.ops.deferrable_sql() 101 103 ] 102 104 pending = False … … 117 119 opts = model._meta 118 120 if model in pending_references: 119 121 for rel_class, f in pending_references[model]: 122 on_delete_clause, on_update_clause = self.on_delete_and_update_clauses(style, f) 120 123 rel_opts = rel_class._meta 121 124 r_table = rel_opts.db_table 122 125 r_col = f.column … … 125 128 # For MySQL, r_name must be unique in the first 64 characters. 126 129 # So we are careful with character usage here. 127 130 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 128 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s ;' % \131 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s%s;' % \ 129 132 (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())), 130 133 qn(r_col), qn(table), qn(col), 134 on_delete_clause, on_update_clause, 131 135 self.connection.ops.deferrable_sql())) 132 136 del pending_references[model] 133 137 return final_output 134 138 139 def on_delete_and_update_clauses(self, style, foreign_key_field): 140 on_delete_clause = '' 141 try: 142 on_delete = getattr(foreign_key_field, 'ON_DELETE_HANDLED_BY_DB') #USED FOR UNIT TESTING ONLY (see comment in modeltests/on_delete_and_update_db/models.py) 143 except AttributeError: 144 on_delete = getattr(settings, 'ON_DELETE_HANDLED_BY_DB', False) 145 if on_delete: 146 on_delete = getattr(foreign_key_field.rel, 'on_delete', None) 147 if on_delete: 148 on_delete_clause = style.SQL_KEYWORD(' ON DELETE %s' % foreign_key_field.rel.on_delete.sql) 149 150 on_update_clause = '' 151 try: 152 on_update = getattr(foreign_key_field, 'ON_UPDATE_HANDLED_BY_DB') #USED FOR UNIT TESTING ONLY; (see comment in modeltests/on_delete_and_update_db/models.py) 153 except AttributeError: 154 on_update = getattr(settings, 'ON_UPDATE_HANDLED_BY_DB', False) 155 if on_update: 156 on_update = getattr(foreign_key_field.rel, 'on_update', None) 157 if on_update: 158 on_update_clause = style.SQL_KEYWORD(' ON UPDATE %s' % foreign_key_field.rel.on_update.sql) 159 160 return on_delete_clause, on_update_clause 161 135 162 def sql_for_many_to_many(self, model, style): 136 163 "Return the CREATE TABLE statments for all the many-to-many tables defined on a model" 137 164 output = [] -
tests/modeltests/on_delete_and_on_update_db/tests.py
Property changes on: tests/modeltests/on_delete_and_on_update_db/__init__.py ___________________________________________________________________ Name: svn:keywords + Id
1 """ 2 Test ON DELETE and ON UPDATE behavior for foreign keys 3 when it's handled by the database 4 (ie. when settings.ON_DELETE_HANDLED_BY_DB=True 5 and settings.ON_UPDATE_HANDLED_BY_DB=True). 6 """ 7 8 from django.conf import settings 9 from django.db import IntegrityError 10 from django.test import TestCase 11 12 from models import * 13 14 15 class ON_DELETE_Tests(TestCase): 16 def setUp(self): 17 self._old = getattr(settings, 'ON_DELETE_HANDLED_BY_DB', False) 18 settings.ON_DELETE_HANDLED_BY_DB = True 19 20 def tearDown(self): 21 settings.ON_DELETE_HANDLED_BY_DB = self._old 22 23 def test_on_delete_SET_NULL_requires_nullable_field(self): 24 try: 25 class Test(models.Model): 26 fk = models.ForeignKey(Data, to_field='data', on_delete=models.SET_NULL) 27 except ValueError, e: 28 self.assertEqual(str(e), "'on_delete=SET_NULL' specified for ForeignKey field 'Test.fk', but the field is not nullable.") 29 else: 30 self.assertTrue(False, 'Expected exception not raised.') 31 32 def test_on_delete_None(self): 33 #SQL behavior is to default to RESTRICT if no ON DELETE clause is specified 34 FKModel = FK_on_delete_None 35 data = Data.objects.create(data=1) 36 FKModel.objects.create(fk=data) 37 self.assertRaises(IntegrityError, data.delete) 38 39 def test_on_delete_CASCADE(self): 40 FKModel = FK_on_delete_CASCADE 41 data = Data.objects.create(data=1) 42 fk_id = FKModel.objects.create(fk=data).id 43 self.assertEqual(data, FKModel.objects.get(id=fk_id).fk) 44 data.delete() 45 self.assertRaises(FKModel.DoesNotExist, FKModel.objects.get, id=fk_id) 46 47 def test_on_delete_RESTRICT(self): 48 FKModel = FK_on_delete_RESTRICT 49 data = Data.objects.create(data=1) 50 FKModel.objects.create(fk=data) 51 self.assertRaises(IntegrityError, data.delete) 52 53 def test_on_delete_SET_NULL(self): 54 FKModel = FK_on_delete_SET_NULL 55 data = Data.objects.create(data=1) 56 fk_id = FKModel.objects.create(fk=data).id 57 self.assertEqual(data, FKModel.objects.get(id=fk_id).fk) 58 self.assertEqual(1, FKModel.objects.get(id=fk_id).fk_id) 59 data.delete() 60 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk) 61 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk_id) 62 63 64 class ON_UPDATE_Tests(TestCase): 65 def setUp(self): 66 self._old = getattr(settings, 'ON_UPDATE_HANDLED_BY_DB', False) 67 settings.ON_UPDATE_HANDLED_BY_DB = True 68 69 def tearDown(self): 70 settings.ON_UPDATE_HANDLED_BY_DB = self._old 71 72 def test_on_update_SET_NULL_requires_nullable_field(self): 73 try: 74 class Test(models.Model): 75 fk = models.ForeignKey(Data, to_field='data', on_update=models.SET_NULL) 76 except ValueError, e: 77 self.assertEqual(str(e), "'on_update=SET_NULL' specified for ForeignKey field 'Test.fk', but the field is not nullable.") 78 else: 79 self.assertTrue(False, 'Expected exception not raised.') 80 81 def test_on_update_None(self): 82 #SQL behavior is to default to RESTRICT if no ON DELETE clause is specified 83 FKModel = FK_on_update_None 84 data = Data.objects.create(data=1) 85 FKModel.objects.create(fk=data) 86 data.data += 1 87 self.assertRaises(IntegrityError, data.save) 88 89 def test_on_update_CASCADE(self): 90 FKModel = FK_on_update_CASCADE 91 data = Data.objects.create(data=1) 92 fk_id = FKModel.objects.create(fk=data).id 93 self.assertEqual(1, FKModel.objects.get(id=fk_id).fk_id) 94 data.data += 1 95 data.save() 96 self.assertEqual(2, FKModel.objects.get(id=fk_id).fk_id) 97 98 def test_on_update_RESTRICT(self): 99 FKModel = FK_on_update_RESTRICT 100 data = Data.objects.create(data=1) 101 FKModel.objects.create(fk=data) 102 data.data += 1 103 self.assertRaises(IntegrityError, data.save) 104 105 def test_on_update_SET_NULL(self): 106 FKModel = FK_on_update_SET_NULL 107 data = Data.objects.create(data=1) 108 fk_id = FKModel.objects.create(fk=data).id 109 self.assertEqual(data, FKModel.objects.get(id=fk_id).fk) 110 self.assertEqual(1, FKModel.objects.get(id=fk_id).fk_id) 111 data.data += 1 112 data.save() 113 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk) 114 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk_id) 115 -
tests/modeltests/on_delete_and_on_update_db/models.py
Property changes on: tests/modeltests/on_delete_and_on_update_db/tests.py ___________________________________________________________________ Name: svn:keywords + Id
1 """ 2 Test ON DELETE and ON UPDATE behavior for foreign keys 3 when it's handled by the database. 4 """ 5 6 from django.db import models 7 8 9 #Note: all the lines marked "Used for unit testing only" mean: 10 #Some tests (the ones modeltests/on_delete_and_update_db/test.py) 11 #need to force SQL generation to include ON UPDATE and ON DELETE clauses, 12 #while others (the ones modeltests/on_delete_and_update_django/test.py) 13 #need to force the SQL not to include these clauses. 14 #Normally the SQL generation is controlled by the settings.py file; 15 #however, usually the database is created all at once when the unit 16 #tests are started, and it uses the settings at the time that happens; 17 #there is no way (that I found) to have different settings for different 18 #unit tests. So, this method of controlling it field-by-field was 19 #added instead; it is intended to be used only by the unit tests. 20 21 22 class Data(models.Model): 23 data = models.IntegerField(unique=True) 24 25 26 class FK_on_delete_None(models.Model): 27 fk = models.ForeignKey(Data, to_field='data', on_delete=None) 28 fk.ON_DELETE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 29 30 class FK_on_delete_CASCADE(models.Model): 31 fk = models.ForeignKey(Data, to_field='data', on_delete=models.CASCADE) 32 fk.ON_DELETE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 33 34 class FK_on_delete_RESTRICT(models.Model): 35 fk = models.ForeignKey(Data, to_field='data', on_delete=models.RESTRICT) 36 fk.ON_DELETE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 37 38 class FK_on_delete_SET_NULL(models.Model): 39 fk = models.ForeignKey(Data, to_field='data', null=True, on_delete=models.SET_NULL) 40 fk.ON_DELETE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 41 42 43 class FK_on_update_None(models.Model): 44 fk = models.ForeignKey(Data, to_field='data', on_update=None) 45 fk.ON_UPDATE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 46 47 class FK_on_update_CASCADE(models.Model): 48 fk = models.ForeignKey(Data, to_field='data', on_update=models.CASCADE) 49 fk.ON_UPDATE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 50 51 class FK_on_update_RESTRICT(models.Model): 52 fk = models.ForeignKey(Data, to_field='data', on_update=models.RESTRICT) 53 fk.ON_UPDATE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) 54 55 class FK_on_update_SET_NULL(models.Model): 56 fk = models.ForeignKey(Data, to_field='data', null=True, on_update=models.SET_NULL) 57 fk.ON_UPDATE_HANDLED_BY_DB = True #Used for unit testing only (see comment above) -
tests/modeltests/on_delete_and_on_update_django/tests.py
Property changes on: tests/modeltests/on_delete_and_on_update_db/models.py ___________________________________________________________________ Name: svn:keywords + Id Property changes on: tests/modeltests/on_delete_and_on_update_django/__init__.py ___________________________________________________________________ Name: svn:keywords + Id
1 """ 2 Test ON DELETE and ON UPDATE behavior for foreign keys 3 when it's handled by the Django 4 (ie. when settings.ON_DELETE_HANDLED_BY_DB=False 5 and settings.ON_UPDATE_HANDLED_BY_DB=False). 6 """ 7 8 from django.conf import settings 9 from django.db import IntegrityError 10 from django.db.models.fields.related import CASCADE, RESTRICT, SET_NULL 11 from django.test import TestCase 12 13 from models import * 14 15 16 class ON_DELETE_Tests(TestCase): 17 def setUp(self): 18 self._old_ON_DELETE_HANDLED_BY_DB = getattr(settings, 'ON_DELETE_HANDLED_BY_DB', False) 19 settings.ON_DELETE_HANDLED_BY_DB = False 20 self._old_ON_DELETE_NONE_HANDLING = getattr(settings, 'ON_DELETE_NONE_HANDLING', CASCADE) 21 22 def tearDown(self): 23 settings.ON_DELETE_HANDLED_BY_DB = self._old_ON_DELETE_HANDLED_BY_DB 24 settings.ON_DELETE_NONE_HANDLING = self._old_ON_DELETE_NONE_HANDLING 25 26 def test_on_delete_SET_NULL_requires_nullable_field(self): 27 try: 28 class Test(models.Model): 29 fk = models.ForeignKey(Data, to_field='data', on_delete=models.SET_NULL) 30 except ValueError, e: 31 self.assertEqual(str(e), "'on_delete=SET_NULL' specified for ForeignKey field 'Test.fk', but the field is not nullable.") 32 else: 33 self.assertTrue(False, 'Expected exception not raised.') 34 35 def test_on_delete_None_CASCADE(self): 36 FKModel = FK_on_delete_None 37 settings.ON_DELETE_NONE_HANDLING = CASCADE 38 data = Data.objects.create(data=1) 39 fk_id = FKModel.objects.create(fk=data).id 40 FKModel.objects.get(id=fk_id) 41 data.delete() 42 self.assertRaises(FKModel.DoesNotExist, FKModel.objects.get, id=fk_id) 43 44 def test_on_delete_None_SET_NULL(self): 45 FKModel = FK_on_delete_None 46 settings.ON_DELETE_NONE_HANDLING = SET_NULL 47 data = Data.objects.create(data=1) 48 fk_id = FKModel.objects.create(fk=data).id 49 self.assertEqual(data, FKModel.objects.get(id=fk_id).fk) 50 self.assertEqual(1, FKModel.objects.get(id=fk_id).fk_id) 51 data.delete() 52 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk) 53 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk_id) 54 55 def test_on_delete_None_SET_NULL_NoNull(self): 56 FKModel = FK_on_delete_None_NoNull 57 settings.ON_DELETE_NONE_HANDLING = SET_NULL 58 data = Data.objects.create(data=1) 59 FKModel.objects.create(fk=data).id 60 self.assertRaises(IntegrityError, data.delete) 61 62 def test_on_delete_None_RESTRICT(self): 63 FKModel = FK_on_delete_None 64 settings.ON_DELETE_NONE_HANDLING = RESTRICT 65 data = Data.objects.create(data=1) 66 FKModel.objects.create(fk=data) 67 self.assertRaises(IntegrityError, data.delete) 68 69 def test_on_delete_CASCADE(self): 70 FKModel = FK_on_delete_CASCADE 71 data = Data.objects.create(data=1) 72 fk_id = FKModel.objects.create(fk=data).id 73 FKModel.objects.get(id=fk_id) 74 data.delete() 75 self.assertRaises(FKModel.DoesNotExist, FKModel.objects.get, id=fk_id) 76 77 def test_on_delete_RESTRICT(self): 78 FKModel = FK_on_delete_RESTRICT 79 data = Data.objects.create(data=1) 80 FKModel.objects.create(fk=data) 81 self.assertRaises(IntegrityError, data.delete) 82 83 def test_on_delete_SET_NULL(self): 84 FKModel = FK_on_delete_SET_NULL 85 data = Data.objects.create(data=1) 86 fk_id = FKModel.objects.create(fk=data).id 87 self.assertEqual(data, FKModel.objects.get(id=fk_id).fk) 88 self.assertEqual(1, FKModel.objects.get(id=fk_id).fk_id) 89 data.delete() 90 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk) 91 self.assertEqual(None, FKModel.objects.get(id=fk_id).fk_id) 92 93 94 class ON_UPDATE_Tests(TestCase): 95 def setUp(self): 96 self._old_ON_UPDATE_HANDLED_BY_DB = getattr(settings, 'ON_UPDATE_HANDLED_BY_DB', False) 97 settings.ON_UPDATE_HANDLED_BY_DB = False 98 99 def tearDown(self): 100 settings.ON_UPDATE_HANDLED_BY_DB = self._old_ON_UPDATE_HANDLED_BY_DB 101 102 def test_on_update_SET_NULL_requires_nullable_field(self): 103 try: 104 class Test(models.Model): 105 fk = models.ForeignKey(Data, to_field='data', on_update=models.SET_NULL) 106 except ValueError, e: 107 self.assertEqual(str(e), "'on_update=SET_NULL' specified for ForeignKey field 'Test.fk', but the field is not nullable.") 108 else: 109 self.assertTrue(False, 'Expected exception not raised.') 110 111 def test_on_update_None(self): 112 #Currently Django has no ON UPDATE behavior at all, 113 #so the default SQL behavior when no ON UPDATE clause 114 #is specified takes over: it acts like ON UPDATE RESTRICT. 115 FKModel = FK_on_update_None 116 data = Data.objects.create(data=1) 117 FKModel.objects.create(fk=data) 118 data.data += 1 119 self.assertRaises(IntegrityError, data.save) 120 121 def test_on_update_CASCADE(self): 122 #Currently Django has no ON UPDATE behavior at all, 123 #so the default SQL behavior when no ON UPDATE clause 124 #is specified takes over: it acts like ON UPDATE RESTRICT. 125 FKModel = FK_on_update_CASCADE 126 data = Data.objects.create(data=1) 127 FKModel.objects.create(fk=data) 128 data.data += 1 129 self.assertRaises(IntegrityError, data.save) 130 131 def test_on_update_RESTRICT(self): 132 #Currently Django has no ON UPDATE behavior at all, 133 #so the default SQL behavior when no ON UPDATE clause 134 #is specified takes over: it acts like ON UPDATE RESTRICT. 135 FKModel = FK_on_update_RESTRICT 136 data = Data.objects.create(data=1) 137 FKModel.objects.create(fk=data) 138 data.data += 1 139 self.assertRaises(IntegrityError, data.save) 140 141 def test_on_update_SET_NULL(self): 142 #Currently Django has no ON UPDATE behavior at all, 143 #so the default SQL behavior when no ON UPDATE clause 144 #is specified takes over: it acts like ON UPDATE RESTRICT. 145 FKModel = FK_on_update_SET_NULL 146 data = Data.objects.create(data=1) 147 FKModel.objects.create(fk=data) 148 data.data += 1 149 self.assertRaises(IntegrityError, data.save) 150 No newline at end of file -
tests/modeltests/on_delete_and_on_update_django/models.py
Property changes on: tests/modeltests/on_delete_and_on_update_django/tests.py ___________________________________________________________________ Name: svn:keywords + Id
1 """ 2 Test ON DELETE and ON UPDATE behavior for foreign keys 3 when it's handled by the Django. 4 """ 5 6 from django.db import models 7 8 9 #Note: for the meaning of the lines marked "Used for unit testing only", 10 #see comment in modeltests/on_delete_and_update_db/models.py 11 12 13 class Data(models.Model): 14 data = models.IntegerField(unique=True) 15 16 17 class FK_on_delete_None(models.Model): 18 fk = models.ForeignKey(Data, to_field='data', null=True, on_delete=None) 19 fk.ON_DELETE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 20 21 class FK_on_delete_None_NoNull(models.Model): 22 fk = models.ForeignKey(Data, to_field='data', on_delete=None) 23 fk.ON_DELETE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 24 25 class FK_on_delete_CASCADE(models.Model): 26 fk = models.ForeignKey(Data, to_field='data', on_delete=models.CASCADE) 27 fk.ON_DELETE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 28 29 class FK_on_delete_RESTRICT(models.Model): 30 fk = models.ForeignKey(Data, to_field='data', on_delete=models.RESTRICT) 31 fk.ON_DELETE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 32 33 class FK_on_delete_SET_NULL(models.Model): 34 fk = models.ForeignKey(Data, to_field='data', null=True, on_delete=models.SET_NULL) 35 fk.ON_DELETE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 36 37 38 class FK_on_update_None(models.Model): 39 fk = models.ForeignKey(Data, to_field='data', on_update=None) 40 fk.ON_UPDATE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 41 42 class FK_on_update_CASCADE(models.Model): 43 fk = models.ForeignKey(Data, to_field='data', on_update=models.CASCADE) 44 fk.ON_UPDATE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 45 46 class FK_on_update_RESTRICT(models.Model): 47 fk = models.ForeignKey(Data, to_field='data', on_update=models.RESTRICT) 48 fk.ON_UPDATE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 49 50 class FK_on_update_SET_NULL(models.Model): 51 fk = models.ForeignKey(Data, to_field='data', null=True, on_update=models.SET_NULL) 52 fk.ON_UPDATE_HANDLED_BY_DB = False #Used for unit testing only (see comment above) 53 54 No newline at end of file -
tests/modeltests/delete/models.py
Property changes on: tests/modeltests/on_delete_and_on_update_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 import Collected Objects49 >>> from django.db.models.query 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, p = CollectedObjects(), CollectedFields() 116 >>> a1._collect_sub_objects(o, p) 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 119 >>> a1.delete() … … 131 131 >>> d2 = D(c=c2, a=a2) 132 132 >>> d2.save() 133 133 134 >>> o = CollectedObjects()135 >>> a2._collect_sub_objects(o )134 >>> o, p = CollectedObjects(), CollectedFields() 135 >>> a2._collect_sub_objects(o, p) 136 136 >>> o.keys() 137 137 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 138 138 >>> a2.delete() … … 163 163 # Since E.f is nullable, we should delete F first (after nulling out 164 164 # the E.f field), then E. 165 165 166 >>> o = CollectedObjects()167 >>> e1._collect_sub_objects(o )166 >>> o, p = CollectedObjects(), CollectedFields() 167 >>> e1._collect_sub_objects(o, p) 168 168 >>> o.keys() 169 169 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 170 170 … … 179 179 180 180 # Same deal as before, though we are starting from the other object. 181 181 182 >>> o = CollectedObjects()183 >>> f2._collect_sub_objects(o )182 >>> o, p = CollectedObjects(), CollectedFields() 183 >>> f2._collect_sub_objects(o, p) 184 184 >>> o.keys() 185 185 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]