mirror of
https://github.com/django/django.git
synced 2025-08-08 04:48:27 +00:00
Fixed #7539, #13067 -- Added on_delete argument to ForeignKey to control cascade behavior. Also refactored deletion for efficiency and code clarity. Many thanks to Johannes Dollinger and Michael Glassford for extensive work on the patch, and to Alex Gaynor, Russell Keith-Magee, and Jacob Kaplan-Moss for review.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14507 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
3ba3294c6b
commit
616b30227d
28 changed files with 850 additions and 608 deletions
|
@ -14,13 +14,6 @@ from django.utils import tree
|
|||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
class CyclicDependency(Exception):
|
||||
"""
|
||||
An error when dealing with a collection of objects that have a cyclic
|
||||
dependency, i.e. when deleting multiple objects.
|
||||
"""
|
||||
pass
|
||||
|
||||
class InvalidQuery(Exception):
|
||||
"""
|
||||
The query passed to raw isn't a safe query to use with raw.
|
||||
|
@ -28,107 +21,6 @@ class InvalidQuery(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class CollectedObjects(object):
|
||||
"""
|
||||
A container that stores keys and lists of values along with remembering the
|
||||
parent objects for all the keys.
|
||||
|
||||
This is used for the database object deletion routines so that we can
|
||||
calculate the 'leaf' objects which should be deleted first.
|
||||
|
||||
previously_seen is an optional argument. It must be a CollectedObjects
|
||||
instance itself; any previously_seen collected object will be blocked from
|
||||
being added to this instance.
|
||||
"""
|
||||
|
||||
def __init__(self, previously_seen=None):
|
||||
self.data = {}
|
||||
self.children = {}
|
||||
if previously_seen:
|
||||
self.blocked = previously_seen.blocked
|
||||
for cls, seen in previously_seen.data.items():
|
||||
self.blocked.setdefault(cls, SortedDict()).update(seen)
|
||||
else:
|
||||
self.blocked = {}
|
||||
|
||||
def add(self, model, pk, obj, parent_model, parent_obj=None, nullable=False):
|
||||
"""
|
||||
Adds an item to the container.
|
||||
|
||||
Arguments:
|
||||
* model - the class of the object being added.
|
||||
* pk - the primary key.
|
||||
* obj - the object itself.
|
||||
* parent_model - the model of the parent object that this object was
|
||||
reached through.
|
||||
* parent_obj - the parent object this object was reached
|
||||
through (not used here, but needed in the API for use elsewhere)
|
||||
* nullable - should be True if this relation is nullable.
|
||||
|
||||
Returns True if the item already existed in the structure and
|
||||
False otherwise.
|
||||
"""
|
||||
if pk in self.blocked.get(model, {}):
|
||||
return True
|
||||
|
||||
d = self.data.setdefault(model, SortedDict())
|
||||
retval = pk in d
|
||||
d[pk] = obj
|
||||
# Nullable relationships can be ignored -- they are nulled out before
|
||||
# deleting, and therefore do not affect the order in which objects
|
||||
# have to be deleted.
|
||||
if parent_model is not None and not nullable:
|
||||
self.children.setdefault(parent_model, []).append(model)
|
||||
return retval
|
||||
|
||||
def __contains__(self, key):
|
||||
return self.data.__contains__(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.data)
|
||||
|
||||
def iteritems(self):
|
||||
for k in self.ordered_keys():
|
||||
yield k, self[k]
|
||||
|
||||
def items(self):
|
||||
return list(self.iteritems())
|
||||
|
||||
def keys(self):
|
||||
return self.ordered_keys()
|
||||
|
||||
def ordered_keys(self):
|
||||
"""
|
||||
Returns the models in the order that they should be dealt with (i.e.
|
||||
models with no dependencies first).
|
||||
"""
|
||||
dealt_with = SortedDict()
|
||||
# Start with items that have no children
|
||||
models = self.data.keys()
|
||||
while len(dealt_with) < len(models):
|
||||
found = False
|
||||
for model in models:
|
||||
if model in dealt_with:
|
||||
continue
|
||||
children = self.children.setdefault(model, [])
|
||||
if len([c for c in children if c not in dealt_with]) == 0:
|
||||
dealt_with[model] = None
|
||||
found = True
|
||||
if not found:
|
||||
raise CyclicDependency(
|
||||
"There is a cyclic dependency of items to be processed.")
|
||||
|
||||
return dealt_with.keys()
|
||||
|
||||
def unordered_keys(self):
|
||||
"""
|
||||
Fallback for the case where is a cyclic dependency but we don't care.
|
||||
"""
|
||||
return self.data.keys()
|
||||
|
||||
class QueryWrapper(object):
|
||||
"""
|
||||
A type that indicates the contents are an SQL fragment and the associate
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue