Clear removed model options in AlterModelOptions - #2280
Merged
waketzheng merged 3 commits intoSep 14, 2026
Merged
Conversation
state_forward merged the new options into the state, so an option dropped from the model (such as a table_description from a deleted docstring) stayed behind and makemigrations re-emitted the same operation on every run.
waketzheng
reviewed
Sep 12, 2026
…tions-remove-keys # Conflicts: # CHANGELOG.rst
waketzheng
approved these changes
Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
AlterModelOptions.state_forwardapplied its options withmodel_state.options.update(...), which can only add or change keys. When an option is removed from a model the generator emits anAlterModelOptionswhose options simply don't contain it, so the old value stayed in the state and the same operation was emitted again on the next run.This clears the keys that belong to this operation and aren't in the new options. The five keys carried by other operations (
table,app,indexes,unique_together,constraints) are left alone — that list already existed as a literal in_model_options_for_compare, so I moved it toAlterModelOptions.UNMANAGED_OPTION_KEYSand had the diff use it. The bug was the two sides disagreeing about which keys this operation owns, so it seemed worth having one definition.Motivation and Context
Fixes #2279.
Deleting a model's docstring clears
table_description, and from then on makemigrations writes the same migration on every run and never gets back toNo changes detected. In a project with a few such models, every later migration carries a block of no-op operations unrelated to the change being made.In practice
table_descriptionis the only key this affects today, sincepk_attris always present.Not covered here:
database_forwardreturnsNoneand table comments are only written byCreateModel, so on PostgreSQL a changed or deleted docstring still never reachesCOMMENT ON TABLE. Same root cause, but it needs SQL emission per backend, so I left it out of this PR.How Has This Been Tested?
Three tests, in the files already covering these units:
test_alter_options_removes_dropped_key— a removed key is gone from the state after the operation applies.test_alter_options_keeps_unmanaged_keys—unique_togetherandtablesurvive, guarding against the clearing being too broad.test_removing_table_description_settles_after_one_migration— generates the operation, applies it, and asserts the next diff is empty. This is the reported symptom.The first and third fail on
developand pass with the change.Also reproduced end to end before and after: a one-model SQLite project, docstring removed,
makemigrations+migratethree times. Before, three identical migrations; after, one, thenNo changes detected.make checkpasses.pytest tests/migrations/is 302 passed, 6 skipped; the full SQLite suite is 1917 passed, 148 skipped, 2 xfailed. One unrelated failure locally,tests/fields/test_time.py::test_zoneinfo, is a missing tzdata on my machine and fails the same way without this change.Checklist: