#22862 closed Bug (fixed)
makemigrations --merge broken because --noinput isn't registered
| Reported by: | Owned by: | Huu Nguyen | |
|---|---|---|---|
| Component: | Migrations | Version: | 1.7-beta-2 |
| Severity: | Release blocker | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
- The
makemigrationsmanagement command doesn't register a--noinputoption with the argument parser. - The logic for the
--mergeoption expects--noinputto populate an instance variable on Command,self.interactive, with False if present, or True by default. - As the option isn't registered,
self.interactiveis None, leading to an incorrect default value, and the command behaves as if--noinputis set. - The non-interactive decision-maker [MigrationQuestioner] is safe/conservative, and appears to silently deny all merge operations.
- Consequently, the command
makemigrations --mergeis effectively broken.
Steps to reproduce
An example Django project is attached, with the default SQlite database configuration.
To reproduce, unpack the archive, create and activate a virtualenv containing django/stable@… (not included),
and cd to the directory containing manage.py.
Assuming you're using virtualenvwrapper,
$ mkvirtualenv django-makemigrations-noinput $ tar -xvzf django-makemigrations-noinput.tar.gz $ cd djangobugs $ pip install -r requirements.txt
The following procedure assumes the same working directory and active virtualenv.
The project's one model, testapp.MergeExample, has had independent modifications requiring migrating.
Makemigrations was run independently, and consequently there are two 0002-series migrations in testapp/migrations.
$ ls -1 testapp/migrations 0001_initial.py 0002_auto_20140618_1441.py 0002_mergeexample_unrelated_int.py __init__.py __pycache__/
$ ./manage.py migrate CommandError: Conflicting migrations detected (0002_auto_20140618_1441, 0002_mergeexample_unrelated_int in testapp). To fix them run 'python manage.py makemigrations --merge'
Clear enough.
$ ./manage.py makemigrations --merge
Merging testapp
Branch 0002_mergeexample_unrelated_int
- Add field unrelated_int to mergeexample
Branch 0002_auto_20140618_1441
- Add field long_title to mergeexample
- Remove field short_title from mergeexample
$ ls -1 testapp/migrations 0001_initial.py 0002_auto_20140618_1441.py 0002_mergeexample_unrelated_int.py __init__.py __pycache__/
Nothing has been changed.
$ ./manage.py migrate CommandError: Conflicting migrations detected (0002_mergeexample_unrelated_int, 0002_auto_20140618_1441 in testapp). To fix them run 'python manage.py makemigrations --merge'
Relevant Django code
The following excerpt from django/core/management/commands/makemigrations.py shows the lines where --noinput
has been omitted, and the code interpreting its absence.
... 17 class Command(BaseCommand): 18 option_list = BaseCommand.option_list + ( 19 make_option('--dry-run', action='store_true', dest='dry_run', default=False, 20 help="Just show what migrations would be made; don't actually write them."), 21 make_option('--merge', action='store_true', dest='merge', default=False, 22 help="Enable fixing of migration conflicts."), 23 make_option('--empty', action='store_true', dest='empty', default=False, 24 help="Create an empty migration."), 25 ) ... 31 def handle(self, *app_labels, **options): 32 33 self.verbosity = int(options.get('verbosity')) 34 self.interactive = options.get('interactive') 35 self.dry_run = options.get('dry_run', False) 36 self.merge = options.get('merge', False) 37 self.empty = options.get('empty', False) ... 151 def handle_merge(self, loader, conflicts): 152 """ 153 Handles merging together conflicted migrations interactively, 154 if it's safe; otherwise, advises on how to fix it. 155 """ 156 if self.interactive: 157 questioner = InteractiveMigrationQuestioner() 158 else: 159 questioner = MigrationQuestioner() ... 195 if questioner.ask_merge(app_label): 196 # If they still want to merge it, then write out an empty 197 # file depending on the migrations needing merging. ...
Temporary workaround
Interestingly, it is possible to work around the lack of a registered --noinput option by passing the option
straight to command programmatically, executing the command as it ought to function.
$ DJANGO_SETTINGS_MODULE=djangobugs.settings python -c 'import django; django.setup(); django.core.management.call_command("makemigrations", merge=True, interactive=True)'
Merging testapp
Branch 0002_auto_20140618_1441
- Add field long_title to mergeexample
- Remove field short_title from mergeexample
Branch 0002_mergeexample_unrelated_int
- Add field unrelated_int to mergeexample
Merging will only work if the operations printed above do not conflict
with each other (working on different fields or models)
Do you want to merge these migration branches? [y/N] y
Created new merge migration /Users/artortenburger/Sites/djangobugs/djangobugs/testapp/migrations/0003_merge.py
$ ./manage.py migrate Operations to perform: Synchronize unmigrated apps: (none) Apply all migrations: admin, sessions, contenttypes, auth, testapp Synchronizing apps without migrations: Creating tables... Installing custom SQL... Installing indexes... Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying sessions.0001_initial... OK Applying testapp.0001_initial... OK Applying testapp.0002_mergeexample_unrelated_int... OK Applying testapp.0002_auto_20140618_1441... OK Applying testapp.0003_merge... FAKED
(Edit: as tarballed, the example contains another change to testapp.MergeExample, which has no migrations yet.
If you successfully run the merge and migrate, re-running migrate will produce a warning about unmigrated changes.
In my test-run, makemigrations and migrate applied it successfully, producing testapp/migrations/0004_remove_mergeexample_unrelated_int.py.)
Proposed resolution
The minimal fix appears to be registering the --noinput option for the makemigrations command.
The two lines have been sourced from django/core/management/commands/migrate.py.
-
makemigrations.py
old new 16 16 17 17 class Command(BaseCommand): 18 18 option_list = BaseCommand.option_list + ( 19 make_option('--noinput', action='store_false', dest='interactive', default=True, 20 help='Tells Django to NOT prompt the user for input of any kind.'), 19 21 make_option('--dry-run', action='store_true', dest='dry_run', default=False, 20 22 help="Just show what migrations would be made; don't actually write them."), 21 23 make_option('--merge', action='store_true', dest='merge', default=False,
A patchfile for django/core/management/commands/makemigrations.py is attached, reproduced above.
Attachments (2)
Change History (7)
by , 11 years ago
| Attachment: | django-makemigrations-noinput.tar.gz added |
|---|
by , 11 years ago
| Attachment: | makemigrations-add-noinput.patch added |
|---|
potential patch for django/core/management/commands/makemigrations.py
comment:1 by , 11 years ago
| Component: | Uncategorized → Migrations |
|---|---|
| Severity: | Normal → Release blocker |
| Triage Stage: | Unreviewed → Accepted |
The patch will be different on master because we are now using argparse.
comment:2 by , 11 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:4 by , 11 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
Example Django project for reproducing the bug