Fixed #22210 -- Saving model instances to non-related fields.
Previously, saving a model instance to a non-related field (in particular a FloatField) would silently convert the model to an Integer (the pk) and save it. This is undesirable behaviour, and likely to cause confusion so the validatio has been hardened. Thanks to @PirosB3 for the patch and @jarshwah for the review.
This commit is contained in:
parent
3bd45ba00d
commit
819e09b848
|
@ -4,6 +4,7 @@ from django.conf import settings
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db.backends.utils import truncate_name
|
from django.db.backends.utils import truncate_name
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
|
from django.db.models.expressions import ExpressionNode
|
||||||
from django.db.models.query_utils import select_related_descend, QueryWrapper
|
from django.db.models.query_utils import select_related_descend, QueryWrapper
|
||||||
from django.db.models.sql.constants import (CURSOR, SINGLE, MULTI, NO_RESULTS,
|
from django.db.models.sql.constants import (CURSOR, SINGLE, MULTI, NO_RESULTS,
|
||||||
ORDER_DIR, GET_ITERATOR_CHUNK_SIZE, SelectInfo)
|
ORDER_DIR, GET_ITERATOR_CHUNK_SIZE, SelectInfo)
|
||||||
|
@ -951,7 +952,14 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||||
values, update_params = [], []
|
values, update_params = [], []
|
||||||
for field, model, val in self.query.values:
|
for field, model, val in self.query.values:
|
||||||
if hasattr(val, 'prepare_database_save'):
|
if hasattr(val, 'prepare_database_save'):
|
||||||
val = val.prepare_database_save(field)
|
if field.rel or isinstance(val, ExpressionNode):
|
||||||
|
val = val.prepare_database_save(field)
|
||||||
|
else:
|
||||||
|
raise TypeError("Database is trying to update a relational field "
|
||||||
|
"of type %s with a value of type %s. Make sure "
|
||||||
|
"you are setting the correct relations" %
|
||||||
|
(field.__class__.__name__,
|
||||||
|
val.__class__.__name__))
|
||||||
else:
|
else:
|
||||||
val = field.get_db_prep_save(val, connection=self.connection)
|
val = field.get_db_prep_save(val, connection=self.connection)
|
||||||
|
|
||||||
|
|
|
@ -674,6 +674,10 @@ Models
|
||||||
the new :attr:`ManyToManyField.through_fields <django.db.models.ManyToManyField.through_fields>`
|
the new :attr:`ManyToManyField.through_fields <django.db.models.ManyToManyField.through_fields>`
|
||||||
argument.
|
argument.
|
||||||
|
|
||||||
|
* Assigning a model instance to a non-relation field will now throw an error.
|
||||||
|
Previously this used to work if the field accepted integers as input as it
|
||||||
|
took the primary key.
|
||||||
|
|
||||||
Signals
|
Signals
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ class Whiz(models.Model):
|
||||||
class BigD(models.Model):
|
class BigD(models.Model):
|
||||||
d = models.DecimalField(max_digits=38, decimal_places=30)
|
d = models.DecimalField(max_digits=38, decimal_places=30)
|
||||||
|
|
||||||
|
class FloatModel(models.Model):
|
||||||
|
size = models.FloatField()
|
||||||
|
|
||||||
|
|
||||||
class BigS(models.Model):
|
class BigS(models.Model):
|
||||||
s = models.SlugField(max_length=255)
|
s = models.SlugField(max_length=255)
|
||||||
|
|
|
@ -23,7 +23,7 @@ from django.utils.functional import lazy
|
||||||
from .models import (
|
from .models import (
|
||||||
Foo, Bar, Whiz, BigD, BigS, BigInt, Post, NullBooleanModel,
|
Foo, Bar, Whiz, BigD, BigS, BigInt, Post, NullBooleanModel,
|
||||||
BooleanModel, PrimaryKeyCharModel, DataModel, Document, RenamedField,
|
BooleanModel, PrimaryKeyCharModel, DataModel, Document, RenamedField,
|
||||||
VerboseNameField, FksToBooleans, FkToChar)
|
VerboseNameField, FksToBooleans, FkToChar, FloatModel)
|
||||||
|
|
||||||
|
|
||||||
class BasicFieldTests(test.TestCase):
|
class BasicFieldTests(test.TestCase):
|
||||||
|
@ -78,6 +78,16 @@ class BasicFieldTests(test.TestCase):
|
||||||
|
|
||||||
self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')
|
self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')
|
||||||
|
|
||||||
|
def test_float_validates_object(self):
|
||||||
|
instance = FloatModel(size=2.5)
|
||||||
|
instance.save()
|
||||||
|
self.assertTrue(instance.id)
|
||||||
|
|
||||||
|
obj = FloatModel.objects.get(pk=1)
|
||||||
|
obj.size = obj
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
obj.save()
|
||||||
|
|
||||||
def test_choices_form_class(self):
|
def test_choices_form_class(self):
|
||||||
"""Can supply a custom choices form class. Regression for #20999."""
|
"""Can supply a custom choices form class. Regression for #20999."""
|
||||||
choices = [('a', 'a')]
|
choices = [('a', 'a')]
|
||||||
|
|
Loading…
Reference in New Issue