|
| 1 | +from collections import Counter |
| 2 | +from operator import attrgetter |
| 3 | + |
| 4 | +from django.db import transaction |
| 5 | +from django.db.models import signals |
| 6 | +from django.db.models.deletion import Collector |
| 7 | + |
| 8 | +from . import cpkquery as sql |
| 9 | + |
| 10 | + |
| 11 | +class CpkCollector(Collector): |
| 12 | + def delete(self): |
| 13 | + # sort instance collections |
| 14 | + for model, instances in self.data.items(): |
| 15 | + self.data[model] = sorted(instances, key=attrgetter("pk")) |
| 16 | + |
| 17 | + # if possible, bring the models in an order suitable for databases that |
| 18 | + # don't support transactions or cannot defer constraint checks until the |
| 19 | + # end of a transaction. |
| 20 | + self.sort() |
| 21 | + # number of objects deleted for each model label |
| 22 | + deleted_counter = Counter() |
| 23 | + |
| 24 | + # Optimize for the case with a single obj and no dependencies |
| 25 | + if len(self.data) == 1 and len(instances) == 1: |
| 26 | + instance = list(instances)[0] |
| 27 | + if self.can_fast_delete(instance): |
| 28 | + with transaction.mark_for_rollback_on_error(self.using): |
| 29 | + count = sql.CPkDeleteQuery(model).delete_batch([instance.pk], self.using) |
| 30 | + setattr(instance, model._meta.pk.attname, None) |
| 31 | + return count, {model._meta.label: count} |
| 32 | + |
| 33 | + with transaction.atomic(using=self.using, savepoint=False): |
| 34 | + # send pre_delete signals |
| 35 | + for model, obj in self.instances_with_model(): |
| 36 | + if not model._meta.auto_created: |
| 37 | + signals.pre_delete.send(sender=model, instance=obj, using=self.using) |
| 38 | + |
| 39 | + # fast deletes |
| 40 | + for qs in self.fast_deletes: |
| 41 | + count = qs._raw_delete(using=self.using) |
| 42 | + if count: |
| 43 | + deleted_counter[qs.model._meta.label] += count |
| 44 | + |
| 45 | + # update fields |
| 46 | + for model, instances_for_fieldvalues in self.field_updates.items(): |
| 47 | + for (field, value), instances in instances_for_fieldvalues.items(): |
| 48 | + query = sql.CPkUpdateQuery(model) |
| 49 | + query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) |
| 50 | + |
| 51 | + # reverse instance collections |
| 52 | + for instances in self.data.values(): |
| 53 | + instances.reverse() |
| 54 | + |
| 55 | + # delete instances |
| 56 | + for model, instances in self.data.items(): |
| 57 | + query = sql.CPkDeleteQuery(model) |
| 58 | + pk_list = [obj.pk for obj in instances] |
| 59 | + count = query.delete_batch(pk_list, self.using) |
| 60 | + if count: |
| 61 | + deleted_counter[model._meta.label] += count |
| 62 | + |
| 63 | + if not model._meta.auto_created: |
| 64 | + for obj in instances: |
| 65 | + signals.post_delete.send(sender=model, instance=obj, using=self.using) |
| 66 | + |
| 67 | + # update collected instances |
| 68 | + for instances_for_fieldvalues in self.field_updates.values(): |
| 69 | + for (field, value), instances in instances_for_fieldvalues.items(): |
| 70 | + for obj in instances: |
| 71 | + setattr(obj, field.attname, value) |
| 72 | + for model, instances in self.data.items(): |
| 73 | + for instance in instances: |
| 74 | + setattr(instance, model._meta.pk.attname, None) |
| 75 | + return sum(deleted_counter.values()), dict(deleted_counter) |
0 commit comments