1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

[5.0.x] Fixed #35422 -- Fixed migrations crash when altering GeneratedField referencing rename field.

Thanks Sarah Boyce for the report and Simon Charette for the
implementation idea.

Backport of 91a4b9a8ec2237434f06866f39c7977e889aeae6 from main.
This commit is contained in:
Mariusz Felisiak 2024-05-02 20:31:51 +02:00 committed by Sarah Boyce
parent 24f54c3b09
commit c544f1a223
4 changed files with 73 additions and 6 deletions

View File

@ -3,6 +3,7 @@ import operator
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.core.exceptions import FieldError
from django.db.backends.ddl_references import ( from django.db.backends.ddl_references import (
Columns, Columns,
Expressions, Expressions,
@ -832,6 +833,7 @@ class BaseDatabaseSchemaEditor:
old_type = old_db_params["type"] old_type = old_db_params["type"]
new_db_params = new_field.db_parameters(connection=self.connection) new_db_params = new_field.db_parameters(connection=self.connection)
new_type = new_db_params["type"] new_type = new_db_params["type"]
modifying_generated_field = False
if (old_type is None and old_field.remote_field is None) or ( if (old_type is None and old_field.remote_field is None) or (
new_type is None and new_field.remote_field is None new_type is None and new_field.remote_field is None
): ):
@ -870,13 +872,19 @@ class BaseDatabaseSchemaEditor:
"through= on M2M fields)" % (old_field, new_field) "through= on M2M fields)" % (old_field, new_field)
) )
elif old_field.generated != new_field.generated or ( elif old_field.generated != new_field.generated or (
new_field.generated new_field.generated and old_field.db_persist != new_field.db_persist
and (
old_field.db_persist != new_field.db_persist
or old_field.generated_sql(self.connection)
!= new_field.generated_sql(self.connection)
)
): ):
modifying_generated_field = True
elif new_field.generated:
try:
old_field_sql = old_field.generated_sql(self.connection)
except FieldError:
# Field used in a generated field was renamed.
modifying_generated_field = True
else:
new_field_sql = new_field.generated_sql(self.connection)
modifying_generated_field = old_field_sql != new_field_sql
if modifying_generated_field:
raise ValueError( raise ValueError(
f"Modifying GeneratedFields is not supported - the field {new_field} " f"Modifying GeneratedFields is not supported - the field {new_field} "
"must be removed and re-added with the new definition." "must be removed and re-added with the new definition."

View File

@ -173,6 +173,18 @@ class DatabaseFeatures(BaseDatabaseFeatures):
}, },
} }
) )
if not self.connection.mysql_is_mariadb:
skips.update(
{
"MySQL doesn't allow renaming columns referenced by generated "
"columns": {
"migrations.test_operations.OperationTests."
"test_invalid_generated_field_changes_on_rename_stored",
"migrations.test_operations.OperationTests."
"test_invalid_generated_field_changes_on_rename_virtual",
},
}
)
return skips return skips
@cached_property @cached_property

View File

@ -27,3 +27,6 @@ Bugfixes
* Fixed a bug in Django 5.0 that caused a migration crash when a * Fixed a bug in Django 5.0 that caused a migration crash when a
``GeneratedField`` was added before any of the referenced fields from its ``GeneratedField`` was added before any of the referenced fields from its
``expression`` definition (:ticket:`35359`). ``expression`` definition (:ticket:`35359`).
* Fixed a bug in Django 5.0 that caused a migration crash when altering a
``GeneratedField`` referencing a rename field (:ticket:`35422`).

View File

@ -5926,6 +5926,50 @@ class OperationTests(OperationTestBase):
def test_invalid_generated_field_changes_virtual(self): def test_invalid_generated_field_changes_virtual(self):
self._test_invalid_generated_field_changes(db_persist=False) self._test_invalid_generated_field_changes(db_persist=False)
def _test_invalid_generated_field_changes_on_rename(self, db_persist):
app_label = "test_igfcor"
operation = migrations.AddField(
"Pony",
"modified_pink",
models.GeneratedField(
expression=F("pink") + F("pink"),
output_field=models.IntegerField(),
db_persist=db_persist,
),
)
project_state, new_state = self.make_test_state(app_label, operation)
# Add generated column.
with connection.schema_editor() as editor:
operation.database_forwards(app_label, editor, project_state, new_state)
# Rename field used in the generated field.
operations = [
migrations.RenameField("Pony", "pink", "renamed_pink"),
migrations.AlterField(
"Pony",
"modified_pink",
models.GeneratedField(
expression=F("renamed_pink"),
output_field=models.IntegerField(),
db_persist=db_persist,
),
),
]
msg = (
"Modifying GeneratedFields is not supported - the field "
f"{app_label}.Pony.modified_pink must be removed and re-added with the "
"new definition."
)
with self.assertRaisesMessage(ValueError, msg):
self.apply_operations(app_label, new_state, operations)
@skipUnlessDBFeature("supports_stored_generated_columns")
def test_invalid_generated_field_changes_on_rename_stored(self):
self._test_invalid_generated_field_changes_on_rename(db_persist=True)
@skipUnlessDBFeature("supports_virtual_generated_columns")
def test_invalid_generated_field_changes_on_rename_virtual(self):
self._test_invalid_generated_field_changes_on_rename(db_persist=False)
@skipUnlessDBFeature( @skipUnlessDBFeature(
"supports_stored_generated_columns", "supports_stored_generated_columns",
"supports_virtual_generated_columns", "supports_virtual_generated_columns",