From e67d7d70fa10c06aca36b9057f82054eda45269d Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 28 Jan 2024 12:02:33 -0500 Subject: Fixed #35149 -- Fixed crashes of db_default with unresolvable output field. Field.db_default accepts either literal Python values or compilables (as_sql) and wrap the former ones in Value internally. While 1e38f11 added support for automatic resolving of output fields for types such as str, int, float, and other unambigous ones it's cannot do so for all types such as dict or even contrib.postgres and contrib.gis primitives. When a literal, non-compilable, value is provided it likely make the most sense to bind its output field to the field its attached to avoid forcing the user to provide an explicit `Value(output_field)`. Thanks David Sanders for the report. --- tests/migrations/test_autodetector.py | 4 ++-- tests/migrations/test_operations.py | 12 ++++++------ tests/schema/tests.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) (limited to 'tests') diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index c54349313e..340805b259 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -1309,7 +1309,7 @@ class AutodetectorTests(BaseAutodetectorTests): changes, "testapp", 0, 0, name="name", preserve_default=True ) self.assertOperationFieldAttributes( - changes, "testapp", 0, 0, db_default=models.Value("Ada Lovelace") + changes, "testapp", 0, 0, db_default="Ada Lovelace" ) @mock.patch( @@ -1515,7 +1515,7 @@ class AutodetectorTests(BaseAutodetectorTests): changes, "testapp", 0, 0, name="name", preserve_default=True ) self.assertOperationFieldAttributes( - changes, "testapp", 0, 0, db_default=models.Value("Ada Lovelace") + changes, "testapp", 0, 0, db_default="Ada Lovelace" ) @mock.patch( diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 5733ba7618..f25bb290a5 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1581,7 +1581,7 @@ class OperationTests(OperationTestBase): self.assertEqual(len(new_state.models[app_label, "pony"].fields), 6) field = new_state.models[app_label, "pony"].fields["height"] self.assertEqual(field.default, models.NOT_PROVIDED) - self.assertEqual(field.db_default, Value(4)) + self.assertEqual(field.db_default, 4) project_state.apps.get_model(app_label, "pony").objects.create(weight=4) self.assertColumnNotExists(table_name, "height") # Add field. @@ -1632,7 +1632,7 @@ class OperationTests(OperationTestBase): self.assertEqual(len(new_state.models[app_label, "pony"].fields), 6) field = new_state.models[app_label, "pony"].fields["special_char"] self.assertEqual(field.default, models.NOT_PROVIDED) - self.assertEqual(field.db_default, Value(db_default)) + self.assertEqual(field.db_default, db_default) self.assertColumnNotExists(table_name, "special_char") with connection.schema_editor() as editor: operation.database_forwards( @@ -1700,7 +1700,7 @@ class OperationTests(OperationTestBase): self.assertEqual(len(new_state.models[app_label, "pony"].fields), 6) field = new_state.models[app_label, "pony"].fields["height"] self.assertEqual(field.default, 3) - self.assertEqual(field.db_default, Value(4)) + self.assertEqual(field.db_default, 4) pre_pony_pk = ( project_state.apps.get_model(app_label, "pony").objects.create(weight=4).pk ) @@ -2145,7 +2145,7 @@ class OperationTests(OperationTestBase): old_weight = project_state.models[app_label, "pony"].fields["weight"] self.assertIs(old_weight.db_default, models.NOT_PROVIDED) new_weight = new_state.models[app_label, "pony"].fields["weight"] - self.assertEqual(new_weight.db_default, Value(4.5)) + self.assertEqual(new_weight.db_default, 4.5) with self.assertRaises(IntegrityError), transaction.atomic(): project_state.apps.get_model(app_label, "pony").objects.create() # Alter field. @@ -2187,7 +2187,7 @@ class OperationTests(OperationTestBase): self.assertIs(old_pink.db_default, models.NOT_PROVIDED) new_pink = new_state.models[app_label, "pony"].fields["pink"] self.assertIs(new_pink.default, models.NOT_PROVIDED) - self.assertEqual(new_pink.db_default, Value(4)) + self.assertEqual(new_pink.db_default, 4) pony = project_state.apps.get_model(app_label, "pony").objects.create(weight=1) self.assertEqual(pony.pink, 3) # Alter field. @@ -2217,7 +2217,7 @@ class OperationTests(OperationTestBase): old_green = project_state.models[app_label, "pony"].fields["green"] self.assertIs(old_green.db_default, models.NOT_PROVIDED) new_green = new_state.models[app_label, "pony"].fields["green"] - self.assertEqual(new_green.db_default, Value(4)) + self.assertEqual(new_green.db_default, 4) old_pony = project_state.apps.get_model(app_label, "pony").objects.create( weight=1 ) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 04ad299aa6..ced3367f00 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -7,6 +7,7 @@ from unittest import mock from django.core.exceptions import FieldError from django.core.management.color import no_style +from django.core.serializers.json import DjangoJSONEncoder from django.db import ( DatabaseError, DataError, @@ -2333,6 +2334,26 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor, self.assertNumQueries(0): editor.alter_field(Author, Author._meta.get_field("name"), new_field) + @isolate_apps("schema") + def test_db_default_output_field_resolving(self): + class Author(Model): + data = JSONField( + encoder=DjangoJSONEncoder, + db_default={ + "epoch": datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + }, + ) + + class Meta: + app_label = "schema" + + with connection.schema_editor() as editor: + editor.create_model(Author) + + author = Author.objects.create() + author.refresh_from_db() + self.assertEqual(author.data, {"epoch": "1970-01-01T00:00:00Z"}) + @skipUnlessDBFeature( "supports_column_check_constraints", "can_introspect_check_constraints" ) -- cgit v1.3