#35595: Generated migration for removing related models can't migrate backwards
-------------------------------------+-------------------------------------
Reporter: Timothy Schilling | Type:
| Uncategorized
Status: new | Component:
| Migrations
Version: 5.0 | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
The migration that's created when removing two related models, results in
a migration that can't be migrated backwards. However, this is only the
case when the model with the foreign key has a `Meta.indexes` specified
that references the foreign key field.
Given the following models:
{{{
class Animal(models.Model):
name = models.CharField(max_length=100)
class Dog(models.Model):
name = models.CharField(max_length=100)
animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
class Meta:
indexes = [models.Index(fields=("animal", "name"))]
}}}
Doing the following results in the error at the end.
1. `python manage.py makemigrations [app]` -> 0001_initial.py (in my test
it was the second migration, in case you can't reproduce it)
2. Remove both models
3. `python manage.py makemigrations [app]` -> 0002_remove.py
4. `python manage.py sqlmigrate [app] 0002 --backwards`
Error:
{{{
❯ ./manage.py sqlmigrate endpoint 0003 --backwards
Traceback (most recent call last):
File "site-packages/django/db/models/options.py", line 681, in get_field
return self.fields_map[field_name]
~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'animal'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/schillingt/Projects/aspiredu/./manage.py", line 11, in
<module>
execute_from_command_line(sys.argv)
File "site-packages/django/core/management/__init__.py", line 442, in
execute_from_command_line
utility.execute()
File "site-packages/django/core/management/__init__.py", line 436, in
execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "site-packages/django/core/management/base.py", line 413, in
run_from_argv
self.execute(*args, **cmd_options)
File "site-packages/django/core/management/commands/sqlmigrate.py", line
38, in execute
return super().execute(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/core/management/base.py", line 459, in
execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/core/management/commands/sqlmigrate.py", line
80, in handle
sql_statements = loader.collect_sql(plan)
^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/db/migrations/loader.py", line 383, in
collect_sql
state = migration.unapply(state, schema_editor, collect_sql=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/db/migrations/migration.py", line 193, in
unapply
operation.database_backwards(
File "site-packages/django/db/migrations/operations/models.py", line
394, in database_backwards
schema_editor.create_model(model)
File "site-packages/django/db/backends/base/schema.py", line 513, in
create_model
self.deferred_sql.extend(self._model_indexes_sql(model))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/db/backends/base/schema.py", line 1624, in
_model_indexes_sql
output.append(index.create_sql(model, self))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/django/db/models/indexes.py", line 112, in
create_sql
model._meta.get_field(field_name)
File "site-packages/django/db/models/options.py", line 683, in get_field
raise FieldDoesNotExist(
django.core.exceptions.FieldDoesNotExist: Dog has no field named 'animal'
}}}
The generated migrations for me are:
0001:
{{{
operations = [
migrations.CreateModel(
name="Animal",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name="Dog",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
(
"animal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="[app].animal",
),
),
],
options={
"indexes": [
models.Index(
fields=["animal", "name"],
name="[app]_do_animal__b82a2f_idx"
)
],
},
),
]
}}}
0002:
{{{
operations = [
migrations.RemoveField(
model_name="dog",
name="animal",
),
migrations.DeleteModel(
name="Animal",
),
migrations.DeleteModel(
name="Dog",
),
]
}}}
My environment is:
- Django 5.0.6
- Postgres 15
- python 3.12.4
- M1 mac
Removing the `Meta.indexes` results in a generated migration that can be
migrated backwards.
This appears by solveable in at least two ways:
1. Adding the following operation to the top of the removal migration
solves the problem.
{{{
migrations.RemoveIndex(
model_name="dog",
name="[app]_do_animal__b82a2f_idx",
),
}}}
2. Removing the `RemoveField` operation from the removal migration and
rearranging the models.
{{{
operations = [
migrations.DeleteModel(
name="Dog",
),
migrations.DeleteModel(
name="Animal",
),
]
}}}
--
Ticket URL: <
https://code.djangoproject.com/ticket/35595>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.