Ticket #16128: #16128-proxy_model_objects_deletion.diff

File #16128-proxy_model_objects_deletion.diff, 8.1 KB (added by Kronuz, 2 years ago)

Not assume all object models are the same as the first one on the set

  • django/db/models/deletion.py

     
    9898        """
    9999        if not objs:
    100100            return []
     101
    101102        new_objs = []
    102         model = objs[0].__class__
    103         instances = self.data.setdefault(model, set())
     103
     104        if source is not None:
     105            source = source._meta.concrete_model
     106
     107        concrete_model_objs = {}
    104108        for obj in objs:
    105             if obj not in instances:
    106                 new_objs.append(obj)
    107         instances.update(new_objs)
    108         # Nullable relationships can be ignored -- they are nulled out before
    109         # deleting, and therefore do not affect the order in which objects have
    110         # to be deleted.
    111         if source is not None and not nullable:
    112             if reverse_dependency:
    113                 source, model = model, source
    114             self.dependencies.setdefault(
    115                 source._meta.concrete_model, set()).add(model._meta.concrete_model)
     109            model = obj.__class__
     110            concrete_model = model._meta.concrete_model
     111            concrete_model_objs.setdefault(concrete_model, {})
     112            concrete_model_objs[concrete_model].setdefault(model, [])
     113            concrete_model_objs[concrete_model][model].append(obj)
     114
     115        for concrete_model, model_objs in concrete_model_objs.iteritems():
     116            for model, objs in model_objs.iteritems():
     117                instances = self.data.setdefault(model, set())
     118                for obj in objs:
     119                    if obj not in instances:
     120                        new_objs.append(obj)
     121                instances.update(new_objs)
     122            # Nullable relationships can be ignored -- they are nulled out before
     123            # deleting, and therefore do not affect the order in which objects have
     124            # to be deleted.
     125            if source is not None and not nullable:
     126                if reverse_dependency:
     127                    source_, concrete_model_ = concrete_model, source
     128                else:
     129                    concrete_model_, source_ = concrete_model, source
     130                self.dependencies.setdefault(
     131                    source_, set()).add(concrete_model_)
    116132        return new_objs
    117133
    118134    def add_batch(self, model, field, objs):
     
    129145        """
    130146        if not objs:
    131147            return
    132         model = objs[0].__class__
    133         self.field_updates.setdefault(
    134             model, {}).setdefault(
    135             (field, value), set()).update(objs)
     148
     149        concrete_model_objs = {}
     150        for obj in objs:
     151            model = obj.__class__
     152            concrete_model = model._meta.concrete_model
     153            concrete_model_objs.setdefault(concrete_model, {})
     154            concrete_model_objs[concrete_model].setdefault(model, [])
     155            concrete_model_objs[concrete_model][model].append(obj)
     156
     157        for concrete_model, model_objs in concrete_model_objs.iteritems():
     158            for model, objs in model_objs.iteritems():
     159                self.field_updates.setdefault(
     160                    model, {}).setdefault(
     161                    (field, value), set()).update(objs)
    136162
    137163    def can_fast_delete(self, objs, from_field=None):
    138164        """
     
    196222        if not new_objs:
    197223            return
    198224
    199         model = new_objs[0].__class__
    200 
    201         # Recursively collect concrete model's parent models, but not their
    202         # related objects. These will be found by meta.get_all_related_objects()
    203         concrete_model = model._meta.concrete_model
    204         for ptr in six.itervalues(concrete_model._meta.parents):
    205             if ptr:
    206                 # FIXME: This seems to be buggy and execute a query for each
    207                 # parent object fetch. We have the parent data in the obj,
    208                 # but we don't have a nice way to turn that data into parent
    209                 # object instance.
    210                 parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
     225        concrete_model_objs = {}
     226        for obj in new_objs:
     227            model = obj.__class__
     228            concrete_model = model._meta.concrete_model
     229            concrete_model_objs.setdefault(concrete_model, {})
     230            concrete_model_objs[concrete_model].setdefault(model, [])
     231            concrete_model_objs[concrete_model][model].append(obj)
     232
     233        for concrete_model, model_objs in concrete_model_objs.iteritems():
     234            parent_objs = []
     235            for model, new_objs in model_objs.iteritems():
     236                # Recursively collect concrete model's parent models, but not their
     237                # related objects. These will be found by meta.get_all_related_objects()
     238                for ptr in six.itervalues(concrete_model._meta.parents):
     239                    if ptr:
     240                        # FIXME: This seems to be buggy and execute a query for each
     241                        # parent object fetch. We have the parent data in the obj,
     242                        # but we don't have a nice way to turn that data into parent
     243                        # object instance.
     244                        parent_objs += [getattr(obj, ptr.name) for obj in new_objs]
     245            if parent_objs:
    211246                self.collect(parent_objs, source=model,
    212247                             source_attr=ptr.rel.related_name,
    213248                             collect_related=False,
    214249                             reverse_dependency=True)
    215250
    216         if collect_related:
    217             for related in model._meta.get_all_related_objects(
    218                     include_hidden=True, include_proxy_eq=True):
    219                 field = related.field
    220                 if field.rel.on_delete == DO_NOTHING:
    221                     continue
    222                 sub_objs = self.related_objects(related, new_objs)
    223                 if self.can_fast_delete(sub_objs, from_field=field):
    224                     self.fast_deletes.append(sub_objs)
    225                 elif sub_objs:
    226                     field.rel.on_delete(self, field, sub_objs, self.using)
    227 
    228             # TODO This entire block is only needed as a special case to
    229             # support cascade-deletes for GenericRelation. It should be
    230             # removed/fixed when the ORM gains a proper abstraction for virtual
    231             # or composite fields, and GFKs are reworked to fit into that.
    232             for relation in model._meta.many_to_many:
    233                 if not relation.rel.through:
    234                     sub_objs = relation.bulk_related_objects(new_objs, self.using)
    235                     self.collect(sub_objs,
    236                                  source=model,
    237                                  source_attr=relation.rel.related_name,
    238                                  nullable=True)
     251            if collect_related:
     252                for model, new_objs in model_objs.iteritems():
     253                    for related in model._meta.get_all_related_objects(
     254                            include_hidden=True, include_proxy_eq=True):
     255                        field = related.field
     256                        if field.rel.on_delete == DO_NOTHING:
     257                            continue
     258                        sub_objs = self.related_objects(related, new_objs)
     259                        if self.can_fast_delete(sub_objs, from_field=field):
     260                            self.fast_deletes.append(sub_objs)
     261                        elif sub_objs:
     262                            field.rel.on_delete(self, field, sub_objs, self.using)
     263
     264                    # TODO This entire block is only needed as a special case to
     265                    # support cascade-deletes for GenericRelation. It should be
     266                    # removed/fixed when the ORM gains a proper abstraction for virtual
     267                    # or composite fields, and GFKs are reworked to fit into that.
     268                    for relation in model._meta.many_to_many:
     269                        if not relation.rel.through:
     270                            sub_objs = relation.bulk_related_objects(new_objs, self.using)
     271                            self.collect(sub_objs,
     272                                         source=model,
     273                                         source_attr=relation.rel.related_name,
     274                                         nullable=True)
    239275
    240276    def related_objects(self, related, objs):
    241277        """
Back to Top