From b4cd8169de604943f8aaee3666282c95e650e5f4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 6 Sep 2013 17:13:51 -0500 Subject: [PATCH] Fixed #11811 -- Data-loss bug in queryset.update. It's now forbidden to call queryset.update(field=instance) when instance hasn't been saved to the database ie. instance.pk is None. --- django/db/models/base.py | 2 ++ tests/queries/models.py | 4 ++++ tests/queries/tests.py | 10 +++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 2ca6f3ecc2..a9d8695f21 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -768,6 +768,8 @@ class Model(six.with_metaclass(ModelBase)): return getattr(self, cachename) def prepare_database_save(self, unused): + if self.pk is None: + raise ValueError("Unsaved model instance %r cannot be used in an ORM query." % self) return self.pk def clean(self): diff --git a/tests/queries/models.py b/tests/queries/models.py index 3ab21661c0..aa72493855 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -17,9 +17,13 @@ class ProxyCategory(DumbCategory): class Meta: proxy = True +@python_2_unicode_compatible class NamedCategory(DumbCategory): name = models.CharField(max_length=10) + def __str__(self): + return self.name + @python_2_unicode_compatible class Tag(models.Model): name = models.CharField(max_length=10) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 9e59a1acee..e58f05c4e5 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -15,6 +15,7 @@ from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature from django.test.utils import str_prefix +from django.utils import six from .models import ( Annotation, Article, Author, Celebrity, Child, Cover, Detail, DumbCategory, @@ -969,7 +970,7 @@ class Queries1Tests(BaseQuerysetTest): q = NamedCategory.objects.filter(tag__parent__isnull=True) self.assertTrue(str(q.query).count('INNER JOIN') == 1) self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1) - self.assertQuerysetEqual( q, ['']) + self.assertQuerysetEqual(q, ['']) def test_ticket_10790_4(self): # Querying across m2m field should not strip the m2m table from join. @@ -1279,6 +1280,13 @@ class Queries4Tests(BaseQuerysetTest): Item.objects.create(name='i1', created=datetime.datetime.now(), note=n1, creator=self.a1) Item.objects.create(name='i2', created=datetime.datetime.now(), note=n1, creator=self.a3) + def test_ticket11811(self): + unsaved_category = NamedCategory(name="Other") + with six.assertRaisesRegex(self, ValueError, + 'Unsaved model instance ' + 'cannot be used in an ORM query.'): + Tag.objects.filter(pk=self.t1.pk).update(category=unsaved_category) + def test_ticket14876(self): # Note: when combining the query we need to have information available # about the join type of the trimmed "creator__isnull" join. If we