diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 154f452967..64ba5b0740 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -613,7 +613,7 @@ class BaseDatabaseSchemaEditor(object): # directly run a (NOT) NULL alteration actions = actions + null_actions # Combine actions together if we can (e.g. postgres) - if self.connection.features.supports_combined_alters: + if self.connection.features.supports_combined_alters and actions: sql, params = tuple(zip(*actions)) actions = [(", ".join(sql), reduce(operator.add, params))] # Apply those actions diff --git a/docs/releases/1.7.2.txt b/docs/releases/1.7.2.txt index 5d4ea56850..d5fe622278 100644 --- a/docs/releases/1.7.2.txt +++ b/docs/releases/1.7.2.txt @@ -37,3 +37,6 @@ Bugfixes * Added support for transactional spatial metadata initialization on SpatiaLite 4.1+ (:ticket:`23152`). + +* Fixed a migration crash that prevented changing a nullable field with a + default to non-nullable with the same default (:ticket:`23738`). diff --git a/tests/schema/models.py b/tests/schema/models.py index ece07194b4..3139838b97 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -17,6 +17,14 @@ class Author(models.Model): apps = new_apps +class AuthorWithDefaultHeight(models.Model): + name = models.CharField(max_length=255) + height = models.PositiveIntegerField(null=True, blank=True, default=42) + + class Meta: + apps = new_apps + + class AuthorWithM2M(models.Model): name = models.CharField(max_length=255) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 3dc986e3a4..4353a1b877 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -7,7 +7,7 @@ from django.db.models.fields import (BinaryField, BooleanField, CharField, Integ PositiveIntegerField, SlugField, TextField) from django.db.models.fields.related import ManyToManyField, ForeignKey from django.db.transaction import atomic -from .models import (Author, AuthorWithM2M, Book, BookWithLongName, +from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, AuthorWithEvenLongerName, BookWeak) @@ -455,6 +455,31 @@ class SchemaTests(TransactionTestCase): self.assertEqual(Author.objects.get(name='Not null author').height, 12) self.assertEqual(Author.objects.get(name='Null author').height, 42) + @unittest.skipUnless(connection.features.supports_combined_alters, "No combined ALTER support") + def test_alter_null_to_not_null_keeping_default(self): + """ + #23738 - Can change a nullable field with default to non-nullable + with the same default. + """ + # Create the table + with connection.schema_editor() as editor: + editor.create_model(AuthorWithDefaultHeight) + # Ensure the field is right to begin with + columns = self.column_classes(AuthorWithDefaultHeight) + self.assertTrue(columns['height'][1][6]) + # Alter the height field to NOT NULL keeping the previous default + new_field = PositiveIntegerField(default=42) + new_field.set_attributes_from_name("height") + with connection.schema_editor() as editor: + editor.alter_field( + AuthorWithDefaultHeight, + AuthorWithDefaultHeight._meta.get_field_by_name("height")[0], + new_field, + ) + # Ensure the field is right afterwards + columns = self.column_classes(AuthorWithDefaultHeight) + self.assertFalse(columns['height'][1][6]) + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") def test_alter_fk(self): """