[1.3.X] Fixed #15776 - delete regression in Django 1.3 involving nullable foreign keys

Many thanks to aaron.l.madison for the detailed report and to emulbreh for
the fix.

Backport of [16295] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16296 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2011-05-30 16:19:53 +00:00
parent 4124ef339c
commit 6e87dacf62
3 changed files with 28 additions and 4 deletions

View File

@ -83,8 +83,8 @@ class Collector(object):
def add(self, objs, source=None, nullable=False, reverse_dependency=False): def add(self, objs, source=None, nullable=False, reverse_dependency=False):
""" """
Adds 'objs' to the collection of objects to be deleted. If the call is Adds 'objs' to the collection of objects to be deleted. If the call is
the result of a cascade, 'source' should be the model that caused it the result of a cascade, 'source' should be the model that caused it,
and 'nullable' should be set to True, if the relation can be null. and 'nullable' should be set to True if the relation can be null.
Returns a list of all objects that were not already collected. Returns a list of all objects that were not already collected.
""" """
@ -100,7 +100,7 @@ class Collector(object):
# Nullable relationships can be ignored -- they are nulled out before # Nullable relationships can be ignored -- they are nulled out before
# deleting, and therefore do not affect the order in which objects have # deleting, and therefore do not affect the order in which objects have
# to be deleted. # to be deleted.
if new_objs and 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, set()).add(model)

View File

@ -51,3 +51,19 @@ class Food(models.Model):
class Eaten(models.Model): class Eaten(models.Model):
food = models.ForeignKey(Food, to_field="name") food = models.ForeignKey(Food, to_field="name")
meal = models.CharField(max_length=20) meal = models.CharField(max_length=20)
# Models for #15776
class Policy(models.Model):
policy_number = models.CharField(max_length=10)
class Version(models.Model):
policy = models.ForeignKey(Policy)
class Location(models.Model):
version = models.ForeignKey(Version, blank=True, null=True)
class Item(models.Model):
version = models.ForeignKey(Version)
location = models.ForeignKey(Location, blank=True, null=True)

View File

@ -5,7 +5,8 @@ from django.db import backend, connection, transaction, DEFAULT_DB_ALIAS
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature 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, Contact, Email, Researcher, Food, Eaten) PlayedWithNote, Contact, Email, Researcher, Food, Eaten,
Policy, Version, Location, Item)
# Can't run this test under SQLite, because you can't # Can't run this test under SQLite, because you can't
@ -102,6 +103,13 @@ class DeleteCascadeTests(TestCase):
# first two asserts just sanity checks, this is the kicker: # first two asserts just sanity checks, this is the kicker:
self.assertEqual(PlayedWithNote.objects.count(), 0) self.assertEqual(PlayedWithNote.objects.count(), 0)
def test_15776(self):
policy = Policy.objects.create(pk=1, policy_number="1234")
version = Version.objects.create(policy=policy)
location = Location.objects.create(version=version)
item = Item.objects.create(version=version, location=location)
policy.delete()
class DeleteCascadeTransactionTests(TransactionTestCase): class DeleteCascadeTransactionTests(TransactionTestCase):
def test_inheritance(self): def test_inheritance(self):