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:
Carl Meyer 2012-03-17 01:24:39 +00:00
parent edcaf8b7ff
commit ddd53dafb5
3 changed files with 37 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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)