diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 3a0dc392384..98afbcc05a8 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -28,12 +28,16 @@ def _is_relevant_relation(relation, altered_field): return altered_field.name in field.to_fields +def _all_related_fields(model): + return model._meta._get_fields(forward=False, reverse=True, include_hidden=True) + + def _related_non_m2m_objects(old_field, new_field): # Filter out m2m objects from reverse relations. # Return (old_relation, new_relation) tuples. return zip( - (obj for obj in old_field.model._meta.related_objects if _is_relevant_relation(obj, old_field)), - (obj for obj in new_field.model._meta.related_objects if _is_relevant_relation(obj, new_field)) + (obj for obj in _all_related_fields(old_field.model) if _is_relevant_relation(obj, old_field)), + (obj for obj in _all_related_fields(new_field.model) if _is_relevant_relation(obj, new_field)), ) @@ -753,7 +757,7 @@ class BaseDatabaseSchemaEditor: # Type alteration on primary key? Then we need to alter the column # referring to us. rels_to_update = [] - if old_field.primary_key and new_field.primary_key and old_type != new_type: + if drop_foreign_keys: rels_to_update.extend(_related_non_m2m_objects(old_field, new_field)) # Changed to become primary key? if self._field_became_primary_key(old_field, new_field): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 33b76984d46..64e29194ca9 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1308,6 +1308,51 @@ class OperationTests(OperationTestBase): operation.database_backwards("test_alflpkfk", editor, new_state, project_state) assertIdTypeEqualsFkType() + @skipUnlessDBFeature('supports_foreign_keys') + def test_alter_field_reloads_state_on_fk_with_to_field_target_type_change(self): + app_label = 'alter_field_reloads_state_on_fk_with_to_field_target_type_change' + project_state = self.apply_operations(app_label, ProjectState(), operations=[ + migrations.CreateModel('Rider', fields=[ + ('id', models.AutoField(primary_key=True)), + ('code', models.PositiveIntegerField(unique=True)), + ]), + migrations.CreateModel('Pony', fields=[ + ('id', models.AutoField(primary_key=True)), + ('rider', models.ForeignKey('%s.Rider' % app_label, models.CASCADE, to_field='code')), + ]), + ]) + operation = migrations.AlterField( + 'Rider', + 'code', + models.CharField(max_length=100, unique=True), + ) + self.apply_operations(app_label, project_state, operations=[operation]) + + @skipUnlessDBFeature('supports_foreign_keys') + def test_alter_field_reloads_state_on_fk_with_to_field_related_name_target_type_change(self): + app_label = 'alter_field_reloads_state_on_fk_with_to_field_rn_target_type_change' + project_state = self.apply_operations(app_label, ProjectState(), operations=[ + migrations.CreateModel('Rider', fields=[ + ('id', models.AutoField(primary_key=True)), + ('code', models.PositiveIntegerField(unique=True)), + ]), + migrations.CreateModel('Pony', fields=[ + ('id', models.AutoField(primary_key=True)), + ('rider', models.ForeignKey( + '%s.Rider' % app_label, + models.CASCADE, + to_field='code', + related_name='+', + )), + ]), + ]) + operation = migrations.AlterField( + 'Rider', + 'code', + models.CharField(max_length=100, unique=True), + ) + self.apply_operations(app_label, project_state, operations=[operation]) + def test_alter_field_reloads_state_on_fk_target_changes(self): """ If AlterField doesn't reload state appropriately, the second AlterField