Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion django/db/backends/base/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ def __init__(self, connection, collect_sql=False, atomic=True):
self.collect_sql = collect_sql
if self.collect_sql:
self.collected_sql = []
# Tables renamed while collecting SQL don't exist under their new
# name in the database, so introspection must target the old name.
self.collected_table_renames = {}
self.atomic_migration = self.connection.features.can_rollback_ddl and atomic

# State-managing methods
Expand Down Expand Up @@ -697,6 +700,14 @@ def alter_db_table(self, model, old_db_table, new_db_table):
"new_table": self.quote_name(new_db_table),
}
)
if self.collect_sql:
# The rename isn't executed, so later introspection of the new
# table name must be redirected to the still-existing old one,
# following any earlier rename of the same table in this batch.
existing_table = self.collected_table_renames.pop(
old_db_table, old_db_table
)
self.collected_table_renames[new_db_table] = existing_table
# Rename all references to the old table name.
for sql in self.deferred_sql:
if isinstance(sql, Statement):
Expand Down Expand Up @@ -2019,9 +2030,12 @@ def _constraint_names(
)
for name in column_names
]
table_name = model._meta.db_table
if self.collect_sql:
table_name = self.collected_table_renames.get(table_name, table_name)
with self.connection.cursor() as cursor:
constraints = self.connection.introspection.get_constraints(
cursor, model._meta.db_table
cursor, table_name
)
result = []
for name, infodict in constraints.items():
Expand Down
24 changes: 24 additions & 0 deletions tests/migrations/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,30 @@ def test_rename_model_with_self_referential_fk(self):
"test_rmwsrf_rider", ["friend_id"], ("test_rmwsrf_horserider", "id")
)

def test_rename_model_with_self_referential_fk_collect_sql(self):
"""
Collecting SQL (e.g. sqlmigrate) for a RenameModel operation on a model
with a self-referential foreign key doesn't introspect the renamed
table, which doesn't exist yet (#33185).
"""
project_state = self.set_up_test_model("test_rmwsrfcs", related_model=True)
operation = migrations.RenameModel("Rider", "HorseRider")
new_state = project_state.clone()
operation.state_forwards("test_rmwsrfcs", new_state)
with connection.schema_editor(collect_sql=True) as editor:
operation.database_forwards(
"test_rmwsrfcs", editor, project_state, new_state
)
collected_sql = "\n".join(editor.collected_sql)
# The table is renamed without crashing on introspection of the
# not-yet-renamed table, and the self-referential FK is handled
# (rather than silently skipped) using the constraint introspected
# from the still-existing old table.
self.assertIn(
connection.ops.quote_name("test_rmwsrfcs_horserider"), collected_sql
)
self.assertIn("friend_id", collected_sql)

def test_rename_model_with_superclass_fk(self):
"""
Tests the RenameModel operation on a model which has a superclass that
Expand Down