Hi,
I thinks there is a bug in django in that particular conditions.
When an app has a squashed migration which include a RemoveField operation and you try to migrate backward on a migration elsewhere (in the app or in another) it raise a KeyError on the removed field.
Steps to reproduce :
- Create Model
class Animal(models.Model):
field_a = models.CharField("A", max_length=50, default="", blank=True)
field_b = models.CharField("B", max_length=50, default="", blank=True)
- Make migrations
makemigrations
=>0001_initial.py
- Remove field B
class Animal(models.Model):
field_a = models.CharField("A", max_length=50, default="", blank=True)
- Make migrations
makemigrations
=>0002_remove_animal_field_b.py
- Add field C
class Animal(models.Model):
field_a = models.CharField("A", max_length=50, default="", blank=True)
field_c = models.CharField("C", max_length=50, default="", blank=True)
- Make migrations
makemigrations
=>0003_animal_field_c.py
- Apply migrations
migrate
(0001,0002,0003) - Squash migrations from 0002 to 0003
squashmigrations myapp 0002 0003
=>0002_remove_animal_field_b_squashed_0003_animal_field_c.py
- Add field D
class Animal(models.Model):
field_a = models.CharField("A", max_length=50, default="", blank=True)
field_c = models.CharField("C", max_length=50, default="", blank=True)
field_d = models.CharField("D", max_length=50, default="", blank=True)
- Make migrations
makemigrations
=>0004_animal_field_d.py
- Apply migrations
migrate
(0004) - Migrate backward
migrate myapp 0003
Raise a KeyError on “field_b”
$ python manage.py migrate myapp 0003
Operations to perform:
Target specific migration: 0003_animal_field_c, from myapp
Traceback (most recent call last):
File "manage.py", line 22, in <module>
main()
~~~~^^
File "manage.py", line 18, in main
execute_from_command_line(sys.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
~~~~~~~~~~~~~~~^^
File "/venv/lib/python3.13/site-packages/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/core/management/base.py", line 416, in run_from_argv
self.execute(*args, **cmd_options)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/core/management/base.py", line 460, in execute
output = self.handle(*args, **options)
File "/venv/lib/python3.13/site-packages/django/core/management/base.py", line 107, in wrapper
res = handle_func(*args, **kwargs)
File "/venv/lib/python3.13/site-packages/django/core/management/commands/migrate.py", line 298, in handle
pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
File "/venv/lib/python3.13/site-packages/django/db/migrations/executor.py", line 91, in _create_project_state
migration.mutate_state(state, preserve=False)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/db/migrations/migration.py", line 91, in mutate_state
operation.state_forwards(self.app_label, new_state)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/db/migrations/operations/fields.py", line 164, in state_forwards
state.remove_field(app_label, self.model_name_lower, self.name)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/venv/lib/python3.13/site-packages/django/db/migrations/state.py", line 271, in remove_field
old_field = model_state.fields.pop(name)
KeyError: 'field_b'
I’ve tried it against django 5.0.14, django 5.1.8, django 5.2, django git master and this particular commit which seems to incldue other fixes on backward bugs: Fixed #35595 -- Generated explicit RemoveIndex operations on model re… · charettes/django@d55d032 · GitHub