#35652: Unapplying a data migration that removes data fails with relations -----------------------------------+-------------------------------------- Reporter: Timothy Schilling | Type: Bug 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 -----------------------------------+-------------------------------------- I believe there's a bug in the plan logic for the `MigrationExecutor` class. When a related model has been removed in an earlier migration, a later data migration that removes data from a model will attempt to run a query to collect/delete data from the related model. Unfortunately, that related model's table has been removed from the database.
Given the following app and model structure: {{{ # App A class A(models.Model): name = models.CharField() # App B class B(models.Model): a = models.ForeignKey(A, on_delete=models.CASCADE) }}} 1. Create migrations 2. Remove model `B`, create migration 3. Apply all migrations forwards 4. `python manage.py makemigrations a --empty --name create_data` {{{ def create_data(apps, schema_editor): A = apps.get_model('a', 'A') A.objects.create(name='test') def remove_data(apps, schema_editor): A = apps.get_model('a', 'A') A.objects.filter(name='test').delete() # In migration class operations = [migrations.RunPython(create_data, remove_data)] }}} 5. Attempt to reverse back to A 0001 (`python manage.py migrate a 0001`) It should break on the reverse of A 0002_create_data, because it will attempt to run a query to delete related `B` instances, but that table has been removed. It appears that the migration app state isn't removing the `B` model. I was able to track this down to at least the `MigrationExecutor.migration_plan` method returning a full plan that puts the all app A migrations before the second app B migration. I found that the RemoveModel operation mutates the state properly, but that `MigrationExecturor._migrate_all_backwards` is using a different state from `states[migration]`. That state is from the `full_plan` that gets passed in which comes from `MigrationExectutor.migration_plan`. Interestingly enough, if we change this part of the code: https://github.com/django/django/blob/main/django/db/migrations/executor.py#L195-L214 To {{{ for migration, _ in full_plan: if not migrations_to_run: # We remove every migration that we applied from this set so # that we can bail out once the last migration has been applied # and don't always run until the very end of the migration # process. break if migration not in migrations_to_run and migration in applied_migrations: # Only mutate the state if the migration is actually applied # to make sure the resulting state doesn't include changes # from unrelated migrations. migration.mutate_state(state, preserve=False) for migration, _ in full_plan: if not migrations_to_run: # We remove every migration that we applied from this set so # that we can bail out once the last migration has been applied # and don't always run until the very end of the migration # process. break if migration in migrations_to_run: if "apps" not in state.__dict__: state.apps # Render all -- performance critical # The state before this migration states[migration] = state # The old state keeps as-is, we continue with the new state state = migration.mutate_state(state, preserve=True) migrations_to_run.remove(migration) }}} It unapply successfully because all the `applied_migrations` are mutating the state before the `migrations_to_run` stores any state. Note: I haven't reproduced this on a fresh project. -- Ticket URL: <https://code.djangoproject.com/ticket/35652> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/010701910e8b0a95-9792863c-12f3-4c13-a450-5fbfb2ceabcc-000000%40eu-central-1.amazonses.com.