Django

Code

Ticket #7539: 7539.on_delete.r11706.diff

File 7539.on_delete.r11706.diff, 26.3 kB (added by emulbreh, 5 months ago)
  • tests/modeltests/on_delete/tests.py

    old new  
     1from django.test import TestCase 
     2from django.db import models, IntegrityError 
     3 
     4class R(models.Model): 
     5    is_default = models.BooleanField(default=False) 
     6 
     7    def __str__(self): 
     8        return "%s" % self.pk 
     9 
     10get_default_r = lambda: R.objects.get_or_create(is_default=True)[0] 
     11     
     12class S(models.Model): 
     13    r = models.ForeignKey(R) 
     14     
     15class T(models.Model): 
     16    s = models.ForeignKey(S) 
     17 
     18class A(models.Model): 
     19    name = models.CharField(max_length=10)     
     20 
     21    auto = models.ForeignKey(R, related_name="auto_set") 
     22    auto_nullable = models.ForeignKey(R, null=True, related_name='auto_nullable_set') 
     23    setnull = models.ForeignKey(R, on_delete=models.SET_NULL, null=True, related_name='setnull_set') 
     24    setdefault = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=get_default_r, related_name='setdefault_set') 
     25    setdefault_none = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=None, null=True, related_name='setnull_nullable_set') 
     26    cascade = models.ForeignKey(R, on_delete=models.CASCADE, related_name='cascade_set') 
     27    cascade_nullable = models.ForeignKey(R, on_delete=models.CASCADE, null=True, related_name='cascade_nullable_set') 
     28    protect = models.ForeignKey(R, on_delete=models.PROTECT, null=True) 
     29    donothing = models.ForeignKey(R, on_delete=models.DO_NOTHING, null=True, related_name='donothing_set') 
     30     
     31def create_a(name): 
     32    a = A(name=name) 
     33    for name in ('auto', 'auto_nullable', 'setnull', 'setdefault', 'setdefault_none', 'cascade', 'cascade_nullable', 'protect', 'donothing'): 
     34        r = R.objects.create() 
     35        setattr(a, name, r) 
     36    a.save() 
     37    return a 
     38 
     39class OnDeleteTests(TestCase): 
     40    def test_basics(self): 
     41        DEFAULT = get_default_r() 
     42         
     43        a = create_a('auto') 
     44        a.auto.delete() 
     45        self.failIf(A.objects.filter(name='auto').exists()) 
     46         
     47        a = create_a('auto_nullable') 
     48        a.auto_nullable.delete() 
     49        self.failUnlessEqual([a], list(A.objects.all())) 
     50         
     51        a = create_a('setnull') 
     52        a.setnull.delete() 
     53        a = A.objects.get(pk=a.pk) 
     54        self.failUnlessEqual(None, a.setnull) 
     55         
     56        a = create_a('setdefault') 
     57        a.setdefault.delete() 
     58        a = A.objects.get(pk=a.pk) 
     59        self.failUnlessEqual(DEFAULT, a.setdefault) 
     60         
     61        a = create_a('setdefault_none') 
     62        a.setdefault_none.delete() 
     63        a = A.objects.get(pk=a.pk) 
     64        self.failUnlessEqual(None, a.setdefault_none) 
     65         
     66        a = create_a('cascade') 
     67        a.cascade.delete() 
     68        self.failIf(A.objects.filter(name='cascade').exists()) 
     69         
     70        a = create_a('cascade_nullable') 
     71        a.cascade_nullable.delete() 
     72        self.failIf(A.objects.filter(name='cascade_nullable').exists()) 
     73         
     74        a = create_a('protect') 
     75        self.assertRaises(IntegrityError, a.protect.delete) 
     76         
     77        # Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model,  
     78        # so we connect to pre_delete and set the fk to a known value. 
     79        replacement_r = R.objects.create() 
     80        def check_do_nothing(sender, **kwargs): 
     81            obj = kwargs['instance'] 
     82            obj.donothing_set.update(donothing=replacement_r) 
     83        models.signals.pre_delete.connect(check_do_nothing) 
     84        a = create_a('do_nothing') 
     85        a.donothing.delete() 
     86        a = A.objects.get(pk=a.pk) 
     87        self.failUnlessEqual(replacement_r, a.donothing) 
     88        models.signals.pre_delete.disconnect(check_do_nothing)         
     89         
     90        # cleanup 
     91        A.objects.all().update(protect=None, donothing=None) 
     92        R.objects.all().delete() 
     93        self.failIf(A.objects.exists()) 
     94         
     95    def test_cache_update(self): 
     96        deleted = [] 
     97        related_setnull_sets = [] 
     98        def pre_delete(sender, **kwargs): 
     99            obj = kwargs['instance'] 
     100            deleted.append(obj) 
     101            if isinstance(obj, R): 
     102                related_setnull_sets.append(list(a.pk for a in obj.setnull_set.all())) 
     103 
     104        models.signals.pre_delete.connect(pre_delete) 
     105        a = create_a('cache_update_setnull') 
     106        a.setnull.delete() 
     107         
     108        a = create_a('cache_update_setnull') 
     109        a.cascade.delete() 
     110         
     111        for obj in deleted: 
     112            self.failUnlessEqual(None, obj.pk) 
     113             
     114        for pk_list in related_setnull_sets: 
     115            for a in A.objects.filter(id__in=pk_list): 
     116                self.failUnlessEqual(None, a.setnull) 
     117         
     118        models.signals.pre_delete.disconnect(pre_delete) 
     119 
     120    def test_deletion_order(self): 
     121        pre_delete_order = [] 
     122        post_delete_order = [] 
     123 
     124        def log_post_delete(sender, **kwargs): 
     125            pre_delete_order.append((sender, kwargs['instance'].pk)) 
     126 
     127        def log_pre_delete(sender, **kwargs): 
     128            post_delete_order.append((sender, kwargs['instance'].pk)) 
     129         
     130        models.signals.post_delete.connect(log_post_delete) 
     131        models.signals.pre_delete.connect(log_pre_delete) 
     132         
     133        r = R.objects.create(pk=1) 
     134        s1 = S.objects.create(pk=1, r=r) 
     135        s2 = S.objects.create(pk=2, r=r) 
     136        t1 = T.objects.create(pk=1, s=s1) 
     137        t2 = T.objects.create(pk=2, s=s2) 
     138        r.delete() 
     139        self.failUnlessEqual(pre_delete_order, [(T, 2), (T, 1), (S, 2), (S, 1), (R, 1)]) 
     140        self.failUnlessEqual(post_delete_order, [(T, 1), (T, 2), (S, 1), (S, 2), (R, 1)]) 
     141         
     142        models.signals.post_delete.disconnect(log_post_delete) 
     143        models.signals.post_delete.disconnect(log_pre_delete) 
     144         
  • tests/modeltests/delete/models.py

    old new  
    4444__test__ = {'API_TESTS': """ 
    4545### Tests for models A,B,C,D ### 
    4646 
    47 ## First, test the CollectedObjects data structure directly 
    48  
    49 >>> from django.db.models.query import CollectedObjects 
    50  
    51 >>> g = CollectedObjects() 
    52 >>> g.add("key1", 1, "item1", None) 
    53 False 
    54 >>> g["key1"] 
    55 {1: 'item1'} 
    56 >>> g.add("key2", 1, "item1", "key1") 
    57 False 
    58 >>> g.add("key2", 2, "item2", "key1") 
    59 False 
    60 >>> g["key2"] 
    61 {1: 'item1', 2: 'item2'} 
    62 >>> g.add("key3", 1, "item1", "key1") 
    63 False 
    64 >>> g.add("key3", 1, "item1", "key2") 
    65 True 
    66 >>> g.ordered_keys() 
    67 ['key3', 'key2', 'key1'] 
    68  
    69 >>> g.add("key2", 1, "item1", "key3") 
    70 True 
    71 >>> g.ordered_keys() 
    72 Traceback (most recent call last): 
    73     ... 
    74 CyclicDependency: There is a cyclic dependency of items to be processed. 
    75  
    76  
    77 ## Second, test the usage of CollectedObjects by Model.delete() 
    78  
    7947# Due to the way that transactions work in the test harness, 
    8048# doing m.delete() here can work but fail in a real situation, 
    8149# since it may delete all objects, but not in the right order. 
     
    11179>>> c1.save() 
    11280>>> d1 = D(c=c1, a=a1) 
    11381>>> d1.save() 
    114  
    115 >>> o = CollectedObjects() 
    116 >>> a1._collect_sub_objects(o) 
    117 >>> o.keys() 
    118 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 
    11982>>> a1.delete() 
    12083 
    12184# Same again with a known bad order 
     
    13093>>> c2.save() 
    13194>>> d2 = D(c=c2, a=a2) 
    13295>>> d2.save() 
    133  
    134 >>> o = CollectedObjects() 
    135 >>> a2._collect_sub_objects(o) 
    136 >>> o.keys() 
    137 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>] 
    13896>>> a2.delete() 
    13997 
    14098### Tests for models E,F - nullable related fields ### 
    14199 
    142 ## First, test the CollectedObjects data structure directly 
    143  
    144 >>> g = CollectedObjects() 
    145 >>> g.add("key1", 1, "item1", None) 
    146 False 
    147 >>> g.add("key2", 1, "item1", "key1", nullable=True) 
    148 False 
    149 >>> g.add("key1", 1, "item1", "key2") 
    150 True 
    151 >>> g.ordered_keys() 
    152 ['key1', 'key2'] 
    153  
    154 ## Second, test the usage of CollectedObjects by Model.delete() 
    155  
    156100>>> e1 = E() 
    157101>>> e1.save() 
    158102>>> f1 = F(e=e1) 
    159103>>> f1.save() 
    160104>>> e1.f = f1 
    161105>>> e1.save() 
    162  
    163 # Since E.f is nullable, we should delete F first (after nulling out 
    164 # the E.f field), then E. 
    165  
    166 >>> o = CollectedObjects() 
    167 >>> e1._collect_sub_objects(o) 
    168 >>> o.keys() 
    169 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 
    170  
    171 # 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 
    179106>>> e1.delete() 
    180 CLEARING FIELD f 
    181107 
    182108>>> e2 = E() 
    183109>>> e2.save() 
     
    185111>>> f2.save() 
    186112>>> e2.f = f2 
    187113>>> e2.save() 
    188  
    189 # Same deal as before, though we are starting from the other object. 
    190  
    191 >>> o = CollectedObjects() 
    192 >>> f2._collect_sub_objects(o) 
    193 >>> o.keys() 
    194 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>] 
    195  
    196114>>> f2.delete() 
    197 CLEARING FIELD f 
    198  
    199 # Put this back to normal 
    200 >>> django.db.models.sql.UpdateQuery = original_class 
    201115""" 
    202116} 
  • django/db/models/base.py

    old new  
    523523             (model_class, {pk_val: obj, pk_val: obj, ...}), ...] 
    524524        """ 
    525525        pk_val = self._get_pk_val() 
    526         if seen_objs.add(self.__class__, pk_val, self, parent, nullable): 
     526        if seen_objs.add(self, parent, nullable): 
    527527            return 
    528528 
    529529        for related in self._meta.get_all_related_objects(): 
     
    534534                except ObjectDoesNotExist: 
    535535                    pass 
    536536                else: 
    537                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null
     537                    related.field.rel.on_delete(self, related, sub_obj, seen_objs
    538538            else: 
    539539                # To make sure we can access all elements, we can't use the 
    540540                # normal manager on the related object. So we work directly 
     
    547547                    raise AssertionError("Should never get here.") 
    548548                delete_qs = rel_descriptor.delete_manager(self).all() 
    549549                for sub_obj in delete_qs: 
    550                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null
     550                    related.field.rel.on_delete(self, related, sub_obj, seen_objs
    551551 
    552552        # Handle any ancestors (for the model-inheritance case). We do this by 
    553553        # traversing to the most remote parent classes -- those with no parents 
  • django/db/models/fields/related.py

    old new  
    1 from django.db import connection, transaction 
     1from django.db import connection, transaction, IntegrityError 
    22from django.db.backends import util 
    33from django.db.models import signals, get_model 
    44from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist 
     
    1818 
    1919RECURSIVE_RELATIONSHIP_CONSTANT = 'self' 
    2020 
     21def CASCADE(obj, related, sub_obj, seen_objs): 
     22    sub_obj._collect_sub_objects(seen_objs, obj.__class__) 
     23 
     24def PROTECT(obj, related, sub_obj, seen_objs): 
     25    msg = '[Django] Cannot delete a parent object: a foreign key constraint fails (ForeignKey `%s` (pk=`%s`) references `%s` (pk=`%s`))' % ( 
     26        sub_obj.__class__, sub_obj.pk, 
     27        obj.__class__, obj.pk, 
     28    ) 
     29    raise IntegrityError(msg) 
     30 
     31def SET(value): 
     32    def set_on_delete(obj, related, sub_obj, seen_objs): 
     33        seen_objs.add(sub_obj, related.field, value) 
     34    return set_on_delete 
     35 
     36def SET_NULL(obj, related, sub_obj, seen_objs): 
     37    seen_objs.add_field_update(sub_obj, related.field, None) 
     38 
     39def SET_DEFAULT(obj, related, sub_obj, seen_objs): 
     40    seen_objs.add_field_update(sub_obj, related.field, related.field.get_default()) 
     41 
     42def DO_NOTHING(obj, related, sub_obj, seen_objs): 
     43    pass 
     44 
     45 
    2146pending_lookups = {} 
    2247 
    2348def add_lazy_relation(cls, field, relation, operation): 
     
    628653 
    629654class ManyToOneRel(object): 
    630655    def __init__(self, to, field_name, related_name=None, 
    631             limit_choices_to=None, lookup_overrides=None, parent_link=False): 
     656            limit_choices_to=None, lookup_overrides=None, parent_link=False, 
     657            on_delete=None): 
    632658        try: 
    633659            to._meta 
    634660        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 
     
    641667        self.lookup_overrides = lookup_overrides or {} 
    642668        self.multiple = True 
    643669        self.parent_link = parent_link 
     670        self.on_delete = on_delete 
    644671 
    645672    def get_related_field(self): 
    646673        """ 
     
    655682 
    656683class OneToOneRel(ManyToOneRel): 
    657684    def __init__(self, to, field_name, related_name=None, 
    658             limit_choices_to=None, lookup_overrides=None, parent_link=False): 
     685            limit_choices_to=None, lookup_overrides=None, parent_link=False,  
     686            on_delete=None): 
    659687        super(OneToOneRel, self).__init__(to, field_name, 
    660688                related_name=related_name, limit_choices_to=limit_choices_to, 
    661                 lookup_overrides=lookup_overrides, parent_link=parent_link) 
     689                lookup_overrides=lookup_overrides, parent_link=parent_link, 
     690                on_delete=on_delete) 
    662691        self.multiple = False 
    663692 
    664693class ManyToManyRel(object): 
     
    692721            assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) 
    693722            to_field = to_field or to._meta.pk.name 
    694723        kwargs['verbose_name'] = kwargs.get('verbose_name', None) 
     724         
     725        on_delete = kwargs.pop('on_delete', None) 
     726        if on_delete is None: 
     727            if kwargs.get('null', False): 
     728                on_delete = SET_NULL 
     729            else: 
     730                on_delete = CASCADE 
    695731 
    696732        kwargs['rel'] = rel_class(to, to_field, 
    697733            related_name=kwargs.pop('related_name', None), 
    698734            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    699735            lookup_overrides=kwargs.pop('lookup_overrides', None), 
    700             parent_link=kwargs.pop('parent_link', False)) 
     736            parent_link=kwargs.pop('parent_link', False), 
     737            on_delete=on_delete) 
    701738        Field.__init__(self, **kwargs) 
    702739 
    703740        self.db_index = True 
     
    742779            target = self.rel.to._meta.db_table 
    743780        cls._meta.duplicate_targets[self.column] = (target, "o2m") 
    744781 
     782        on_delete = self.rel.on_delete 
     783        if on_delete == SET_NULL and not self.null: 
     784            specification = "'on_delete=SET_NULL'" 
     785            raise ValueError("%s specified for %s '%s.%s', but the field is not nullable." % (specification, type(self).__name__, cls.__name__, name)) 
     786        if on_delete == SET_DEFAULT and not self.has_default(): 
     787            specification = "'on_delete=SET_DEFAULT'" 
     788            raise ValueError("%s specified for %s '%s.%s', but the field has no default value." % (specification, type(self).__name__, cls.__name__, name)) 
     789 
    745790    def contribute_to_related_class(self, cls, related): 
    746791        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 
    747792 
  • 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, SET_NULL, SET_DEFAULT, DO_NOTHING 
    1415from django.db.models import signals 
    1516 
    1617# Admin stages. 
  • django/db/models/query.py

    old new  
    10111011    else: 
    10121012        forced_managed = False 
    10131013    try: 
    1014         ordered_classes = seen_objs.keys() 
    1015     except CyclicDependency: 
    1016         # If there is a cyclic dependency, we cannot in general delete the 
    1017         # objects.  However, if an appropriate transaction is set up, or if the 
    1018         # database is lax enough, it will succeed. So for now, we go ahead and 
    1019         # try anyway. 
    1020         ordered_classes = seen_objs.unordered_keys() 
     1014        try: 
     1015            seen_objs.sort() 
     1016        except CyclicDependency: 
     1017            pass 
    10211018 
    1022     obj_pairs = {} 
    1023     try: 
    1024         for cls in ordered_classes: 
    1025             items = seen_objs[cls].items() 
    1026             items.sort() 
    1027             obj_pairs[cls] = items 
     1019        for obj in seen_objs: 
     1020            signals.pre_delete.send(sender=obj.__class__, instance=obj) 
     1021             
     1022        seen_objs.execute_update() 
    10281023 
    1029             # Pre-notify all instances to be deleted. 
    1030             for pk_val, instance in items: 
    1031                 signals.pre_delete.send(sender=cls, instance=instance) 
     1024        seen_objs.execute_delete_related() 
     1025         
     1026        seen_objs.reverse_instances() 
     1027         
     1028        seen_objs.execute_delete() 
    10321029 
    1033             pk_list = [pk for pk,instance in items] 
    1034             del_query = sql.DeleteQuery(cls, connection) 
    1035             del_query.delete_batch_related(pk_list) 
     1030        for obj in seen_objs: 
     1031            signals.post_delete.send(sender=obj.__class__, instance=obj) 
     1032             
     1033        seen_objs.update_instances() 
    10361034 
    1037             update_query = sql.UpdateQuery(cls, connection) 
    1038             for field, model in cls._meta.get_fields_with_model(): 
    1039                 if (field.rel and field.null and field.rel.to in seen_objs and 
    1040                         filter(lambda f: f.column == field.rel.get_related_field().column, 
    1041                         field.rel.to._meta.fields)): 
    1042                     if model: 
    1043                         sql.UpdateQuery(model, connection).clear_related(field, 
    1044                                 pk_list) 
    1045                     else: 
    1046                         update_query.clear_related(field, pk_list) 
    1047  
    1048         # Now delete the actual data. 
    1049         for cls in ordered_classes: 
    1050             items = obj_pairs[cls] 
    1051             items.reverse() 
    1052  
    1053             pk_list = [pk for pk,instance in items] 
    1054             del_query = sql.DeleteQuery(cls, connection) 
    1055             del_query.delete_batch(pk_list) 
    1056  
    1057             # Last cleanup; set NULLs where there once was a reference to the 
    1058             # object, NULL the primary key of the found objects, and perform 
    1059             # post-notification. 
    1060             for pk_val, instance in items: 
    1061                 for field in cls._meta.fields: 
    1062                     if field.rel and field.null and field.rel.to in seen_objs: 
    1063                         setattr(instance, field.attname, None) 
    1064  
    1065                 signals.post_delete.send(sender=cls, instance=instance) 
    1066                 setattr(instance, cls._meta.pk.attname, None) 
    1067  
    10681035        if forced_managed: 
    10691036            transaction.commit() 
    10701037        else: 
  • django/db/models/query_utils.py

    old new  
    3939    """ 
    4040 
    4141    def __init__(self, previously_seen=None): 
     42        # {model: {(field, value): set([instances])}} 
     43        self.field_updates = {} 
    4244        self.data = {} 
    4345        self.children = {} 
    4446        if previously_seen: 
    45             self.blocked = previously_seen.blocked 
    46             for cls, seen in previously_seen.data.items(): 
    47                 self.blocked.setdefault(cls, SortedDict()).update(seen) 
     47            self.blocked = set(previously_seen) 
     48            self.blocked.update(previously_seen.blocked) 
    4849        else: 
    49             self.blocked = {} 
     50            self.blocked = set() 
    5051 
    51     def add(self, model, pk, obj, parent_model, nullable=False): 
     52    def add(self, obj, parent_model, nullable=False): 
    5253        """ 
    5354        Adds an item to the container. 
    5455 
    5556        Arguments: 
    56         * model - the class of the object being added. 
    57         * pk - the primary key. 
    5857        * obj - the object itself. 
    5958        * parent_model - the model of the parent object that this object was 
    6059          reached through. 
     
    6261 
    6362        Returns True if the item already existed in the structure and 
    6463        False otherwise. 
    65         """ 
    66         if pk in self.blocked.get(model, {})
     64        """         
     65        if obj in self.blocked
    6766            return True 
    6867 
    69         d = self.data.setdefault(model, SortedDict()) 
    70         retval = pk in d 
    71         d[pk] = obj 
     68        model = obj.__class__ 
     69        instances = self.data.setdefault(model, []) 
     70        seen_before = obj in instances 
     71        if not seen_before: 
     72            instances.append(obj) 
    7273        # Nullable relationships can be ignored -- they are nulled out before 
    7374        # deleting, and therefore do not affect the order in which objects 
    7475        # have to be deleted. 
    7576        if parent_model is not None and not nullable: 
    76             self.children.setdefault(parent_model, []).append(model) 
    77         return retval 
     77            self.children.setdefault(parent_model, set()).add(model) 
     78        return seen_before 
     79         
     80    def add_field_update(self, obj, field, value): 
     81        updates = self.field_updates.setdefault(obj.__class__, dict()) 
     82        updates.setdefault((field, value), set()).add(obj) 
     83         
     84    def __iter__(self): 
     85        for instances in self.data.itervalues(): 
     86            for obj in instances: 
     87                yield obj 
    7888 
    79     def __contains__(self, key): 
    80         return self.data.__contains__(key) 
    81  
    82     def __getitem__(self, key): 
    83         return self.data[key] 
    84  
    8589    def __nonzero__(self): 
    8690        return bool(self.data) 
    8791 
    88     def iteritems(self): 
    89         for k in self.ordered_keys(): 
    90             yield k, self[k] 
    91  
    92     def items(self): 
    93         return list(self.iteritems()) 
    94  
    95     def keys(self): 
    96         return self.ordered_keys() 
    97  
    98     def ordered_keys(self): 
    99         """ 
    100         Returns the models in the order that they should be dealt with (i.e. 
    101         models with no dependencies first). 
    102         """ 
    103         dealt_with = SortedDict() 
    104         # Start with items that have no children 
     92    def sort(self): 
     93        for instances in self.data.itervalues(): 
     94            instances.sort(key=lambda obj: obj.pk) 
     95         
     96        sorted_models = [] 
    10597        models = self.data.keys() 
    106         while len(dealt_with) < len(models): 
     98        while len(sorted_models) < len(models): 
    10799            found = False 
    108100            for model in models: 
    109                 if model in dealt_with
     101                if model in sorted_models
    110102                    continue 
    111                 children = self.children.setdefault(model, []
    112                 if len([c for c in children if c not in dealt_with]) == 0
    113                     dealt_with[model] = None 
     103                children = self.children.get(model
     104                if not children or not children.difference(sorted_models)
     105                    sorted_models.append(model) 
    114106                    found = True 
    115107            if not found: 
    116                 raise CyclicDependency( 
    117                     "There is a cyclic dependency of items to be processed."
     108                raise CyclicDependency() 
     109        self.data = SortedDict([(model, self.data[model]) for model in sorted_models]
    118110 
    119         return dealt_with.keys() 
     111    def reverse_instances(self): 
     112        for instances in self.data.itervalues(): 
     113            instances.reverse() 
    120114 
    121     def unordered_keys(self): 
    122         """ 
    123         Fallback for the case where is a cyclic dependency but we don't  care. 
    124         """ 
    125         return self.data.keys() 
     115    def execute_delete_related(self): 
     116        from django.db.models import connection, sql 
     117        for model, instances in self.data.iteritems(): 
     118            pk_list = [obj.pk for obj in instances] 
     119            del_query = sql.DeleteQuery(model, connection) 
     120            del_query.delete_batch_related(pk_list) 
    126121 
     122    def execute_delete(self): 
     123        from django.db.models import connection, sql 
     124        for model, instances in self.data.iteritems(): 
     125            pk_list = [obj.pk for obj in instances] 
     126            del_query = sql.DeleteQuery(model, connection) 
     127            del_query.delete_batch(pk_list) 
     128 
     129    def execute_update(self): 
     130        from django.db.models import connection, sql 
     131        from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE 
     132        for model, instances_for_fieldvalues in self.field_updates.iteritems(): 
     133            for (field, value), instances in instances_for_fieldvalues.iteritems():                 
     134                pk_field = model._meta.pk 
     135                pk_list = [obj.pk for obj in instances] 
     136                for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): 
     137                    query = sql.UpdateQuery(model, connection) 
     138                    query.where = query.where_class() 
     139                    query.where.add((sql.where.Constraint(None, pk_field.column, pk_field), 'in', pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]), sql.where.AND) 
     140                    query.add_update_values({field.name: value}) 
     141                    query.execute_sql() 
     142 
     143    def update_instances(self): 
     144        for model, instances_for_fieldvalues in self.field_updates.iteritems(): 
     145            for (field, value), instances in instances_for_fieldvalues.iteritems(): 
     146                for obj in instances: 
     147                    setattr(obj, field.attname, value) 
     148        for model, instances in self.data.iteritems(): 
     149            for instance in instances: 
     150                setattr(instance, model._meta.pk.attname, None) 
     151 
     152 
    127153class QueryWrapper(object): 
    128154    """ 
    129155    A type that indicates the contents are an SQL fragment and the associate