Django

Code

Ticket #7539: on_delete_on_update-r11733.diff

File on_delete_on_update-r11733.diff, 45.7 kB (added by glassfordm, 4 months ago)

Same as my previous patch, but including unintentionally omitted unit tests

  • django/db/models/base.py

    old new  
    88from django.db.models.fields import AutoField, FieldDoesNotExist 
    99from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 
    1010from django.db.models.query import delete_objects, Q 
    11 from django.db.models.query_utils import CollectedObjects, DeferredAttribute 
     11from django.db.models.query_utils import CollectedFields, CollectedObjects, DeferredAttribute 
    1212from django.db.models.options import Options 
    13 from django.db import connection, transaction, DatabaseError 
     13from django.db import connection, transaction, DatabaseError, IntegrityError 
    1414from django.db.models import signals 
     15from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT 
    1516from django.db.models.loading import register_models, get_model 
    1617from django.utils.functional import curry 
    1718from django.utils.encoding import smart_str, force_unicode, smart_unicode 
     
    516517 
    517518    save_base.alters_data = True 
    518519 
    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): 
    520521        """ 
    521522        Recursively populates seen_objs with all objects related to this 
    522523        object. 
     
    528529        pk_val = self._get_pk_val() 
    529530        if seen_objs.add(self.__class__, pk_val, self, parent, nullable): 
    530531            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') 
    531576 
    532577        for related in self._meta.get_all_related_objects(): 
    533578            rel_opts_name = related.get_accessor_name() 
    534579            if isinstance(related.field.rel, OneToOneRel): 
    535580                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. 
    536586                    sub_obj = getattr(self, rel_opts_name) 
    537587                except ObjectDoesNotExist: 
    538588                    pass 
    539589                else: 
    540                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null
     590                    _handle_sub_obj(related, sub_obj
    541591            else: 
    542592                # To make sure we can access all elements, we can't use the 
    543593                # normal manager on the related object. So we work directly 
     
    555605                        continue 
    556606                delete_qs = rel_descriptor.delete_manager(self).all() 
    557607                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
    559609 
    560610        # Handle any ancestors (for the model-inheritance case). We do this by 
    561611        # traversing to the most remote parent classes -- those with no parents 
     
    570620                continue 
    571621            # At this point, parent_obj is base class (no ancestor models). So 
    572622            # 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
    574624 
    575625    def delete(self): 
    576626        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) 
    577627 
    578628        # Find all the objects than need to be deleted. 
    579629        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) 
    581632 
    582633        # Actually delete the objects. 
    583         delete_objects(seen_objs) 
    584  
     634        delete_objects(seen_objs, fields_to_set) 
    585635    delete.alters_data = True 
    586636 
    587637    def _get_FIELD_display(self, field): 
  • django/db/models/fields/related.py

    old new  
    2020 
    2121pending_lookups = {} 
    2222 
     23class CASCADE(object): 
     24    pass 
     25class PROTECT(object): 
     26    pass 
     27class SET_NULL(object): 
     28    pass 
     29class SET_DEFAULT(object): 
     30    pass 
     31ALLOWED_ON_DELETE_ACTION_TYPES = set([None, CASCADE, PROTECT, SET_NULL, SET_DEFAULT]) 
     32 
    2333def add_lazy_relation(cls, field, relation, operation): 
    2434    """ 
    2535    Adds a lookup on ``cls`` when a related field is defined using a string, 
     
    222232        # object you just set. 
    223233        setattr(instance, self.cache_name, value) 
    224234        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 
    225245 
    226246class ReverseSingleRelatedObjectDescriptor(object): 
    227247    # This class provides the functionality that makes the related-object 
     
    625645 
    626646class ManyToOneRel(object): 
    627647    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): 
    629650        try: 
    630651            to._meta 
    631652        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 
     
    638659        self.lookup_overrides = lookup_overrides or {} 
    639660        self.multiple = True 
    640661        self.parent_link = parent_link 
     662        self.on_delete = on_delete 
    641663 
    642664    def is_hidden(self): 
    643665        "Should the related object be hidden?" 
     
    656678 
    657679class OneToOneRel(ManyToOneRel): 
    658680    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): 
    660683        super(OneToOneRel, self).__init__(to, field_name, 
    661684                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) 
    663687        self.multiple = False 
    664688 
    665689class ManyToManyRel(object): 
     
    705729            related_name=kwargs.pop('related_name', None), 
    706730            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    707731            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)) 
    709734        Field.__init__(self, **kwargs) 
    710735 
    711736        self.db_index = True 
     
    750775            target = self.rel.to._meta.db_table 
    751776        cls._meta.duplicate_targets[self.column] = (target, "o2m") 
    752777 
     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 
    753788    def contribute_to_related_class(self, cls, related): 
    754789        # Internal FK's - i.e., those with a related name ending with '+' - 
    755790        # don't get a related descriptor. 
  • django/db/models/__init__.py

    old new  
    1111from django.db.models.fields.subclassing import SubfieldBase 
    1212from django.db.models.fields.files import FileField, ImageField 
    1313from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel 
     14from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT 
    1415from django.db.models import signals 
    1516 
    1617# Admin stages. 
  • django/db/models/query.py

    old new  
    55from copy import deepcopy 
    66from django.db import connection, transaction, IntegrityError 
    77from django.db.models.aggregates import Aggregate 
     8from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 
    89from django.db.models.fields import DateField 
    9 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory 
     10from django.db.models.query_utils import Q, select_related_descend, CollectedFields, CollectedObjects, CyclicDependency, deferred_class_factory 
    1011from django.db.models import signals, sql 
    1112 
    1213# Used to control how many objects are worked with at once in some cases (e.g. 
     
    384385            # Collect all the objects to be deleted in this chunk, and all the 
    385386            # objects that are related to the objects that are to be deleted. 
    386387            seen_objs = CollectedObjects(seen_objs) 
     388            fields_to_set = CollectedFields() 
    387389            for object in del_query[:CHUNK_SIZE]: 
    388                 object._collect_sub_objects(seen_objs
     390                object._collect_sub_objects(seen_objs, fields_to_set
    389391 
    390392            if not seen_objs: 
    391393                break 
    392             delete_objects(seen_objs
     394            delete_objects(seen_objs, fields_to_set
    393395 
    394396        # Clear the result cache, in case this QuerySet gets reused. 
    395397        self._result_cache = None 
     
    10001002                setattr(obj, f.get_cache_name(), rel_obj) 
    10011003    return obj, index_end 
    10021004 
    1003 def delete_objects(seen_objs): 
     1005def delete_objects(seen_objs, fields_to_set): 
    10041006    """ 
    10051007    Iterate through a list of seen classes, and remove any instances that are 
    10061008    referred to. 
     
    10211023 
    10221024    obj_pairs = {} 
    10231025    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                     
    10241039        for cls in ordered_classes: 
    10251040            items = seen_objs[cls].items() 
    10261041            items.sort() 
     
    10311046                if not cls._meta.auto_created: 
    10321047                    signals.pre_delete.send(sender=cls, instance=instance) 
    10331048 
     1049            # Handle related GenericRelation and ManyToManyField instances 
    10341050            pk_list = [pk for pk,instance in items] 
    10351051            del_query = sql.DeleteQuery(cls, connection) 
    10361052            del_query.delete_batch_related(pk_list) 
    10371053 
    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 and 
    1041                         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. 
    10501054        for cls in ordered_classes: 
    10511055            items = obj_pairs[cls] 
    10521056            items.reverse() 
    1053  
    10541057            pk_list = [pk for pk,instance in items] 
    10551058            del_query = sql.DeleteQuery(cls, connection) 
    10561059            del_query.delete_batch(pk_list) 
    10571060 
    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() 
    10611072            for pk_val, instance in items: 
    10621073                for field in cls._meta.fields: 
    10631074                    if field.rel and field.null and field.rel.to in seen_objs: 
  • django/db/models/query_utils.py

    old new  
    124124        """ 
    125125        return self.data.keys() 
    126126 
     127class 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 
    127177class QueryWrapper(object): 
    128178    """ 
    129179    A type that indicates the contents are an SQL fragment and the associate 
  • tests/modeltests/on_delete_django/tests.py

    old new  
     1from django.conf import settings 
     2from django.db import IntegrityError, models 
     3from django.db.models.fields.related import CASCADE, PROTECT, SET_NULL, SET_DEFAULT 
     4from django.test import TestCase 
     5 
     6from models import * 
     7 
     8class 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) 
  • tests/modeltests/on_delete_django/models.py

    old new  
     1""" 
     2Test ON DELETE behavior for foreign keys 
     3when it's handled by the Django. 
     4""" 
     5 
     6from django.db import models 
     7 
     8### Data model 
     9 
     10class Data(models.Model): 
     11    data = models.IntegerField(unique=True)     
     12 
     13def default_data(): 
     14    data, _ = Data.objects.get_or_create(data=1000) 
     15    return data 
     16 
     17### ForeignKey models 
     18 
     19class ForeignKey_CASCADE(models.Model): 
     20    fk = models.ForeignKey(Data, on_delete=models.CASCADE) 
     21 
     22class ForeignKey_PROTECT(models.Model): 
     23    fk = models.ForeignKey(Data, on_delete=models.PROTECT) 
     24 
     25class ForeignKey_SET_NULL(models.Model): 
     26    fk = models.ForeignKey(Data, null=True, on_delete=models.SET_NULL) 
     27 
     28class ForeignKey_SET_DEFAULT(models.Model): 
     29    fk = models.ForeignKey(Data, default=default_data, on_delete=models.SET_DEFAULT) 
     30 
     31class ForeignKey_None_Null_True(models.Model): 
     32    fk = models.ForeignKey(Data, null=True, on_delete=None) 
     33 
     34class ForeignKey_None_Null_False(models.Model): 
     35    fk = models.ForeignKey(Data, on_delete=None) 
     36 
     37### OneToOneField models 
     38 
     39class OneToOne_CASCADE(models.Model): 
     40    fk = models.OneToOneField(Data, on_delete=models.CASCADE) 
     41 
     42class OneToOne_PROTECT(models.Model): 
     43    fk = models.OneToOneField(Data, on_delete=models.PROTECT) 
     44 
     45class OneToOne_SET_NULL(models.Model): 
     46    fk = models.OneToOneField(Data, null=True, on_delete=models.SET_NULL) 
     47 
     48class OneToOne_SET_DEFAULT(models.Model): 
     49    fk = models.OneToOneField(Data, default=default_data, on_delete=models.SET_DEFAULT) 
     50 
     51class OneToOne_None_Null_True(models.Model): 
     52    fk = models.OneToOneField(Data, null=True, on_delete=None) 
     53 
     54class OneToOne_None_Null_False(models.Model): 
     55    fk = models.OneToOneField(Data, on_delete=None) 
     56     
     57### Multiple FK models 
     58 
     59class 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') 
  • tests/modeltests/delete/models.py

    old new  
    4646 
    4747## First, test the CollectedObjects data structure directly 
    4848 
    49 >>> from django.db.models.query import CollectedObjects 
     49>>> from django.db.models.query_utils import CollectedFields, CollectedObjects 
    5050 
    5151>>> g = CollectedObjects() 
    5252>>> g.add("key1", 1, "item1", None) 
     
    112112>>> d1 = D(c=c1, a=a1) 
    113113>>> d1.save() 
    114114 
    115 >>> o = CollectedObjects() 
    116 >>> a1._collect_sub_objects(o
     115>>> o, f = CollectedObjects(), CollectedFields() 
     116>>> a1._collect_sub_objects(o, f
    117117>>> o.keys() 
    118118[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 
     119>>> f.keys() 
     120[] 
    119121>>> a1.delete() 
    120122 
    121123# Same again with a known bad order 
     
    131133>>> d2 = D(c=c2, a=a2) 
    132134>>> d2.save() 
    133135 
    134 >>> o = CollectedObjects() 
    135 >>> a2._collect_sub_objects(o
     136>>> o, f = CollectedObjects(), CollectedFields() 
     137>>> a2._collect_sub_objects(o, f
    136138>>> o.keys() 
    137139[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 
     140>>> f.keys() 
     141[] 
    138142>>> a2.delete() 
    139143 
    140144### Tests for models E,F - nullable related fields ### 
     
    163167# Since E.f is nullable, we should delete F first (after nulling out 
    164168# the E.f field), then E. 
    165169 
    166 >>> o = CollectedObjects() 
    167 >>> e1._collect_sub_objects(o
     170>>> o, f = CollectedObjects(), CollectedFields() 
     171>>> e1._collect_sub_objects(o, f
    168172>>> o.keys() 
    169173[<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 
     174>>> f.keys() 
     175[<class 'modeltests.delete.models.E'>] 
    170176 
    171 # temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first 
    172 >>> import django.db.models.sql 
    173 >>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery): 
    174 ...     def clear_related(self, related_field, pk_list): 
    175 ...         print "CLEARING FIELD",related_field.name 
    176 ...         return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list) 
    177 >>> original_class = django.db.models.sql.UpdateQuery 
    178 >>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery 
    179177>>> e1.delete() 
    180 CLEARING FIELD f 
    181178 
    182179>>> e2 = E() 
    183180>>> e2.save() 
     
    188185 
    189186# Same deal as before, though we are starting from the other object. 
    190187 
    191 >>> o = CollectedObjects() 
    192 >>> f2._collect_sub_objects(o
     188>>> o, f = CollectedObjects(), CollectedFields() 
     189>>> f2._collect_sub_objects(o, f
    193190>>> 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'>] 
    195194 
    196195>>> f2.delete() 
    197 CLEARING FIELD f 
    198  
    199 # Put this back to normal 
    200 >>> django.db.models.sql.UpdateQuery = original_class 
    201196""" 
    202197}