Fixed #17918 - Handle proxy models correctly when sorting deletions for databases without deferred constraints. Thanks Nate Bragg for the report.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17756 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
edcaf8b7ff
commit
ddd53dafb5
|
@ -76,6 +76,12 @@ class Collector(object):
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.batches = {} # {model: {field: set([instances])}}
|
self.batches = {} # {model: {field: set([instances])}}
|
||||||
self.field_updates = {} # {model: {(field, value): set([instances])}}
|
self.field_updates = {} # {model: {(field, value): set([instances])}}
|
||||||
|
|
||||||
|
# Tracks deletion-order dependency for databases without transactions
|
||||||
|
# or ability to defer constraint checks. Only concrete model classes
|
||||||
|
# should be included, as the dependencies exist only between actual
|
||||||
|
# database tables; proxy models are represented here by their concrete
|
||||||
|
# parent.
|
||||||
self.dependencies = {} # {model: set([models])}
|
self.dependencies = {} # {model: set([models])}
|
||||||
|
|
||||||
def add(self, objs, source=None, nullable=False, reverse_dependency=False):
|
def add(self, objs, source=None, nullable=False, reverse_dependency=False):
|
||||||
|
@ -101,7 +107,8 @@ class Collector(object):
|
||||||
if source is not None and not nullable:
|
if source is not None and not nullable:
|
||||||
if reverse_dependency:
|
if reverse_dependency:
|
||||||
source, model = model, source
|
source, model = model, source
|
||||||
self.dependencies.setdefault(source, set()).add(model)
|
self.dependencies.setdefault(
|
||||||
|
source._meta.concrete_model, set()).add(model._meta.concrete_model)
|
||||||
return new_objs
|
return new_objs
|
||||||
|
|
||||||
def add_batch(self, model, field, objs):
|
def add_batch(self, model, field, objs):
|
||||||
|
@ -197,15 +204,17 @@ class Collector(object):
|
||||||
|
|
||||||
def sort(self):
|
def sort(self):
|
||||||
sorted_models = []
|
sorted_models = []
|
||||||
|
concrete_models = set()
|
||||||
models = self.data.keys()
|
models = self.data.keys()
|
||||||
while len(sorted_models) < len(models):
|
while len(sorted_models) < len(models):
|
||||||
found = False
|
found = False
|
||||||
for model in models:
|
for model in models:
|
||||||
if model in sorted_models:
|
if model in sorted_models:
|
||||||
continue
|
continue
|
||||||
dependencies = self.dependencies.get(model)
|
dependencies = self.dependencies.get(model._meta.concrete_model)
|
||||||
if not (dependencies and dependencies.difference(sorted_models)):
|
if not (dependencies and dependencies.difference(concrete_models)):
|
||||||
sorted_models.append(model)
|
sorted_models.append(model)
|
||||||
|
concrete_models.add(model._meta.concrete_model)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
return
|
return
|
||||||
|
|
|
@ -90,4 +90,6 @@ class FooFile(models.Model):
|
||||||
class FooPhoto(models.Model):
|
class FooPhoto(models.Model):
|
||||||
my_photo = models.ForeignKey(Photo)
|
my_photo = models.ForeignKey(Photo)
|
||||||
|
|
||||||
|
class FooFileProxy(FooFile):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
|
from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
|
||||||
PlayedWithNote, Email, Researcher, Food, Eaten, Policy, Version, Location,
|
PlayedWithNote, Email, Researcher, Food, Eaten, Policy, Version, Location,
|
||||||
Item, Image, File, Photo, FooFile, FooImage, FooPhoto)
|
Item, Image, File, Photo, FooFile, FooImage, FooPhoto, FooFileProxy)
|
||||||
|
|
||||||
|
|
||||||
# Can't run this test under SQLite, because you can't
|
# Can't run this test under SQLite, because you can't
|
||||||
|
@ -237,3 +237,24 @@ class ProxyDeleteTest(TestCase):
|
||||||
# to it.
|
# to it.
|
||||||
self.assertEqual(len(FooFile.objects.all()), 0)
|
self.assertEqual(len(FooFile.objects.all()), 0)
|
||||||
self.assertEqual(len(FooImage.objects.all()), 0)
|
self.assertEqual(len(FooImage.objects.all()), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_proxy_pair(self):
|
||||||
|
"""
|
||||||
|
If a pair of proxy models are linked by an FK from one concrete parent
|
||||||
|
to the other, deleting one proxy model cascade-deletes the other, and
|
||||||
|
the deletion happens in the right order (not triggering an
|
||||||
|
IntegrityError on databases unable to defer integrity checks).
|
||||||
|
|
||||||
|
Refs #17918.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Create an Image (proxy of File) and FooFileProxy (proxy of FooFile,
|
||||||
|
# which has an FK to File)
|
||||||
|
image = Image.objects.create()
|
||||||
|
as_file = File.objects.get(pk=image.pk)
|
||||||
|
FooFileProxy.objects.create(my_file=as_file)
|
||||||
|
|
||||||
|
Image.objects.all().delete()
|
||||||
|
|
||||||
|
self.assertEqual(len(FooFileProxy.objects.all()), 0)
|
||||||
|
|
Loading…
Reference in New Issue