4
4
from django.db import connections, transaction, IntegrityError
5
5
from django.db.models import signals, sql
6
6
from django.utils.datastructures import SortedDict
7
from django.utils import six
9
10
class ProtectedError(IntegrityError):
77
78
self.batches = {} # {model: {field: set([instances])}}
78
79
self.field_updates = {} # {model: {(field, value): set([instances])}}
80
# fast_deletes is a list of queryset-likes that can be deleted without
81
# fetching the objects into memory.
82
self.fast_deletes = []
80
84
# Tracks deletion-order dependency for databases without transactions
81
85
# or ability to defer constraint checks. Only concrete model classes
130
134
model, {}).setdefault(
131
135
(field, value), set()).update(objs)
137
def can_fast_delete(self, objs, from_field=None):
139
Determines if the objects in the given queryset-like can be
140
fast-deleted. This can be done if there are no cascades, no
141
parents and no signal listeners for the object class.
143
The 'from_field' tells where we are coming from - we need this to
144
determine if the objects are in fact to be deleted. Allows also
145
skipping parent -> child -> parent chain preventing fast delete of
148
if from_field and from_field.rel.on_delete is not CASCADE:
150
if not (hasattr(objs, 'model') and hasattr(objs, '_raw_delete')):
153
if (signals.pre_delete.has_listeners(model)
154
or signals.post_delete.has_listeners(model)
155
or signals.m2m_changed.has_listeners(model)):
157
# The use of from_field comes from the need to avoid cascade back to
158
# parent when parent delete is cascading to child.
160
if any(link != from_field for link in opts.concrete_model._meta.parents.values()):
162
# Foreign keys pointing to this model, both from m2m and other
164
for related in opts.get_all_related_objects(
165
include_hidden=True, include_proxy_eq=True):
166
if related.field.rel.on_delete is not DO_NOTHING:
169
for relation in opts.many_to_many:
170
if not relation.rel.through:
133
174
def collect(self, objs, source=None, nullable=False, collect_related=True,
134
175
source_attr=None, reverse_dependency=False):
147
188
models, the one case in which the cascade follows the forwards
148
189
direction of an FK rather than the reverse direction.)
191
if self.can_fast_delete(objs):
192
self.fast_deletes.append(objs)
150
194
new_objs = self.add(objs, source, nullable,
151
195
reverse_dependency=reverse_dependency)
155
199
model = new_objs[0].__class__
157
# Recursively collect parent models, but not their related objects.
158
# These will be found by meta.get_all_related_objects()
159
for parent_model, ptr in model._meta.parents.iteritems():
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):
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
161
210
parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
162
211
self.collect(parent_objs, source=model,
163
212
source_attr=ptr.rel.related_name,
168
217
for related in model._meta.get_all_related_objects(
169
218
include_hidden=True, include_proxy_eq=True):
170
219
field = related.field
171
if related.model._meta.auto_created:
172
self.add_batch(related.model, field, new_objs)
174
sub_objs = self.related_objects(related, new_objs)
220
if field.rel.on_delete == DO_NOTHING:
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)
177
226
field.rel.on_delete(self, field, sub_objs, self.using)
179
228
# TODO This entire block is only needed as a special case to
200
249
def instances_with_model(self):
201
for model, instances in self.data.iteritems():
250
for model, instances in six.iteritems(self.data):
202
251
for obj in instances:
206
255
sorted_models = []
207
256
concrete_models = set()
208
models = self.data.keys()
257
models = list(self.data)
209
258
while len(sorted_models) < len(models):
211
260
for model in models:
239
288
sender=model, instance=obj, using=self.using
292
for qs in self.fast_deletes:
293
qs._raw_delete(using=self.using)
243
for model, instances_for_fieldvalues in self.field_updates.iteritems():
296
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
244
297
query = sql.UpdateQuery(model)
245
for (field, value), instances in instances_for_fieldvalues.iteritems():
298
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
246
299
query.update_batch([obj.pk for obj in instances],
247
300
{field.name: value}, self.using)
249
302
# reverse instance collections
250
for instances in self.data.itervalues():
303
for instances in six.itervalues(self.data):
251
304
instances.reverse()
254
for model, batches in self.batches.iteritems():
307
for model, batches in six.iteritems(self.batches):
255
308
query = sql.DeleteQuery(model)
256
for field, instances in batches.iteritems():
309
for field, instances in six.iteritems(batches):
257
310
query.delete_batch([obj.pk for obj in instances], self.using, field)
259
312
# delete instances
260
for model, instances in self.data.iteritems():
313
for model, instances in six.iteritems(self.data):
261
314
query = sql.DeleteQuery(model)
262
315
pk_list = [obj.pk for obj in instances]
263
316
query.delete_batch(pk_list, self.using)
272
325
# update collected instances
273
for model, instances_for_fieldvalues in self.field_updates.iteritems():
274
for (field, value), instances in instances_for_fieldvalues.iteritems():
326
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
327
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
275
328
for obj in instances:
276
329
setattr(obj, field.attname, value)
277
for model, instances in self.data.iteritems():
330
for model, instances in six.iteritems(self.data):
278
331
for instance in instances:
279
332
setattr(instance, model._meta.pk.attname, None)