1
0
mirror of https://github.com/django/django.git synced 2025-10-23 21:59:11 +00:00

Fixed #10811 -- Made assigning unsaved objects to FK, O2O, and GFK raise ValueError.

This prevents silent data loss.

Thanks Aymeric Augustin for the initial patch and Loic Bistuer for the review.
This commit is contained in:
Anubhav Joshi
2014-05-19 14:15:55 +05:30
committed by Tim Graham
parent 4f72e5f03a
commit 5643a3b51b
12 changed files with 141 additions and 76 deletions

View File

@@ -109,8 +109,9 @@ class UtilTests(SimpleTestCase):
simple_function = lambda obj: SIMPLE_FUNCTION
site_obj = Site.objects.create(domain=SITE_NAME)
article = Article(
site=Site(domain=SITE_NAME),
site=site_obj,
title=TITLE_TEXT,
created=CREATED_DATE,
)

View File

@@ -209,6 +209,27 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
errors = checks.run_checks()
self.assertEqual(errors, ['performed!'])
def test_unsaved_instance_on_generic_foreign_key(self):
"""
#10811 -- Assigning an unsaved object to GenericForeignKey
should raise an exception.
"""
class Model(models.Model):
content_type = models.ForeignKey(ContentType, null=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
author = Author(name='Author')
model = Model()
model.content_object = None # no error here as content_type allows None
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (author, author._meta.object_name)):
model.content_object = author # raised ValueError here as author is unsaved
author.save()
model.content_object = author # no error because the instance is saved
class GenericRelationshipTests(IsolatedModelsTestCase):

View File

@@ -66,8 +66,10 @@ class ManyToOneRegressionTests(TestCase):
# Creation using keyword argument and unsaved related instance (#8070).
p = Parent()
c = Child(parent=p)
self.assertTrue(c.parent is p)
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (p, Child.parent.field.rel.to._meta.object_name)):
Child(parent=p)
# Creation using attname keyword argument and an id will cause the
# related object to be fetched.

View File

@@ -476,8 +476,6 @@ class QueryTestCase(TestCase):
dive = Book.objects.using('other').create(title="Dive into Python",
published=datetime.date(2009, 5, 4))
mark = Person.objects.using('other').create(name="Mark Pilgrim")
# Set a foreign key with an object from a different database
with self.assertRaises(ValueError):
dive.editor = marty
@@ -492,54 +490,6 @@ class QueryTestCase(TestCase):
with transaction.atomic(using='default'):
marty.edited.add(dive)
# BUT! if you assign a FK object when the base object hasn't
# been saved yet, you implicitly assign the database for the
# base object.
chris = Person(name="Chris Mills")
html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
# initially, no db assigned
self.assertEqual(chris._state.db, None)
self.assertEqual(html5._state.db, None)
# old object comes from 'other', so the new object is set to use 'other'...
dive.editor = chris
html5.editor = mark
self.assertEqual(chris._state.db, 'other')
self.assertEqual(html5._state.db, 'other')
# ... but it isn't saved yet
self.assertEqual(list(Person.objects.using('other').values_list('name', flat=True)),
['Mark Pilgrim'])
self.assertEqual(list(Book.objects.using('other').values_list('title', flat=True)),
['Dive into Python'])
# When saved (no using required), new objects goes to 'other'
chris.save()
html5.save()
self.assertEqual(list(Person.objects.using('default').values_list('name', flat=True)),
['Marty Alchin'])
self.assertEqual(list(Person.objects.using('other').values_list('name', flat=True)),
['Chris Mills', 'Mark Pilgrim'])
self.assertEqual(list(Book.objects.using('default').values_list('title', flat=True)),
['Pro Django'])
self.assertEqual(list(Book.objects.using('other').values_list('title', flat=True)),
['Dive into HTML5', 'Dive into Python'])
# This also works if you assign the FK in the constructor
water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark)
self.assertEqual(water._state.db, 'other')
# ... but it isn't saved yet
self.assertEqual(list(Book.objects.using('default').values_list('title', flat=True)),
['Pro Django'])
self.assertEqual(list(Book.objects.using('other').values_list('title', flat=True)),
['Dive into HTML5', 'Dive into Python'])
# When saved, the new book goes to 'other'
water.save()
self.assertEqual(list(Book.objects.using('default').values_list('title', flat=True)),
['Pro Django'])
self.assertEqual(list(Book.objects.using('other').values_list('title', flat=True)),
['Dive into HTML5', 'Dive into Python', 'Dive into Water'])
def test_foreign_key_deletion(self):
"Cascaded deletions of Foreign Key relations issue queries on the right database"
mark = Person.objects.using('other').create(name="Mark Pilgrim")
@@ -1148,6 +1098,7 @@ class RouterTestCase(TestCase):
# old object comes from 'other', so the new object is set to use the
# source of 'other'...
self.assertEqual(dive._state.db, 'other')
chris.save()
dive.editor = chris
html5.editor = mark

View File

@@ -30,6 +30,10 @@ class Restaurant(models.Model):
return "%s the restaurant" % self.place.name
class Bar(models.Model):
place = models.OneToOneField(Place, null=True)
@python_2_unicode_compatible
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant)

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import transaction, IntegrityError
from django.test import TestCase
from .models import (Place, Restaurant, Waiter, ManualPrimaryKey, RelatedModel,
from .models import (Place, Restaurant, Bar, Waiter, ManualPrimaryKey, RelatedModel,
MultiModel)
@@ -128,3 +128,20 @@ class OneToOneTests(TestCase):
with self.assertRaises(IntegrityError):
with transaction.atomic():
mm.save()
def test_unsaved_object(self):
"""
#10811 -- Assigning an unsaved object to a OneToOneField
should raise an exception.
"""
place = Place(name='User', address='London')
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (place, Restaurant.place.field.rel.to._meta.object_name)):
Restaurant.objects.create(place=place, serves_hot_dogs=True, serves_pizza=False)
bar = Bar()
p = Place(name='User', address='London')
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (bar, p._meta.object_name)):
p.bar = bar

View File

@@ -96,11 +96,6 @@ class OneToOneRegressionTests(TestCase):
r = Restaurant(place=p)
self.assertTrue(r.place is p)
# Creation using keyword argument and unsaved related instance (#8070).
p = Place()
r = Restaurant(place=p)
self.assertTrue(r.place is p)
# Creation using attname keyword argument and an id will cause the related
# object to be fetched.
p = Place.objects.get(name="Demon Dogs")