Ticket #7539: 7539.on_delete.r11706.diff

File 7539.on_delete.r11706.diff, 26.3 KB (added by Johannes Dollinger, 14 years ago)
  • tests/modeltests/on_delete/tests.py

     
     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

     
    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

     
    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

     
    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

     
    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

     
    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

     
    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
Back to Top