From 63fc91b3cac523690655c1d86d09972e0cc3a29d Mon Sep 17 00:00:00 2001 From: Moayad Mardini Date: Thu, 22 May 2014 12:42:46 +0300 Subject: [PATCH] Fixed #22676 -- makemigrations --dry-run should not ask for defaults Made the fix in InteractiveMigrationQuestioner class code, rather than MigrationAutodetector, because --dry-run shouldn't affect whether MigrationAutodetector will detect non-nullable fields, but the questioner should skip the question and returns a None for default (since that won't be used anyway) if --dry-run is used. --- .../management/commands/makemigrations.py | 2 +- django/db/migrations/questioner.py | 67 ++++++++++--------- tests/migrations/test_commands.py | 20 ++++++ .../0001_initial.py | 23 +++++++ .../test_migrations_no_default/__init__.py | 0 5 files changed, 79 insertions(+), 33 deletions(-) create mode 100644 tests/migrations/test_migrations_no_default/0001_initial.py create mode 100644 tests/migrations/test_migrations_no_default/__init__.py diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index 84a4d59fe2..97661ea8b5 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -77,7 +77,7 @@ class Command(BaseCommand): autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), - InteractiveMigrationQuestioner(specified_apps=app_labels), + InteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run), ) # If they want to make an empty migration, make one for each app diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index 4619bd616a..0476ba749f 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -18,9 +18,10 @@ class MigrationQuestioner(object): interactive subclass is what the command-line arguments will use. """ - def __init__(self, defaults=None, specified_apps=None): + def __init__(self, defaults=None, specified_apps=None, dry_run=None): self.defaults = defaults or {} self.specified_apps = specified_apps or set() + self.dry_run = dry_run def ask_initial(self, app_label): "Should we create an initial migration for the app?" @@ -93,37 +94,39 @@ class InteractiveMigrationQuestioner(MigrationQuestioner): def ask_not_null_addition(self, field_name, model_name): "Adding a NOT NULL field to a model" - choice = self._choice_input( - "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) + - "we can't do that (the database needs something to populate existing rows).\n" + - "Please select a fix:", - [ - "Provide a one-off default now (will be set on all existing rows)", - "Quit, and let me add a default in models.py", - ] - ) - if choice == 2: - sys.exit(3) - else: - print("Please enter the default value now, as valid Python") - print("The datetime module is available, so you can do e.g. datetime.date.today()") - while True: - if six.PY3: - # Six does not correctly abstract over the fact that - # py3 input returns a unicode string, while py2 raw_input - # returns a bytestring. - code = input(">>> ") - else: - code = input(">>> ").decode(sys.stdin.encoding) - if not code: - print("Please enter some code, or 'exit' (with no quotes) to exit.") - elif code == "exit": - sys.exit(1) - else: - try: - return eval(code, {}, {"datetime": datetime_safe}) - except (SyntaxError, NameError) as e: - print("Invalid input: %s" % e) + if not self.dry_run: + choice = self._choice_input( + "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) + + "we can't do that (the database needs something to populate existing rows).\n" + + "Please select a fix:", + [ + "Provide a one-off default now (will be set on all existing rows)", + "Quit, and let me add a default in models.py", + ] + ) + if choice == 2: + sys.exit(3) + else: + print("Please enter the default value now, as valid Python") + print("The datetime module is available, so you can do e.g. datetime.date.today()") + while True: + if six.PY3: + # Six does not correctly abstract over the fact that + # py3 input returns a unicode string, while py2 raw_input + # returns a bytestring. + code = input(">>> ") + else: + code = input(">>> ").decode(sys.stdin.encoding) + if not code: + print("Please enter some code, or 'exit' (with no quotes) to exit.") + elif code == "exit": + sys.exit(1) + else: + try: + return eval(code, {}, {"datetime": datetime_safe}) + except (SyntaxError, NameError) as e: + print("Invalid input: %s" % e) + return None def ask_rename(self, model_name, old_name, new_name, field_instance): "Was this field really renamed?" diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 757d69995b..2f78ee27a4 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -6,6 +6,7 @@ import os import shutil from django.apps import apps +from django.db import models from django.core.management import call_command, CommandError from django.db.migrations import questioner from django.test import override_settings, override_system_checks @@ -362,3 +363,22 @@ class MakeMigrationsTests(MigrationTestBase): self.assertIn("Merging migrations", stdout.getvalue()) self.assertIn("Branch 0002_second", stdout.getvalue()) self.assertIn("Branch 0002_conflicting_second", stdout.getvalue()) + + @override_system_checks([]) + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_default"}) + def test_makemigrations_dry_run(self): + """ + Ticket #22676 -- `makemigrations --dry-run` should not ask for defaults. + """ + + class SillyModel(models.Model): + silly_field = models.BooleanField(default=False) + silly_date = models.DateField() # Added field without a default + + class Meta: + app_label = "migrations" + + stdout = six.StringIO() + call_command("makemigrations", "migrations", dry_run=True, stdout=stdout) + # Output the expected changes directly, without asking for defaults + self.assertIn("Add field silly_date to sillymodel", stdout.getvalue()) diff --git a/tests/migrations/test_migrations_no_default/0001_initial.py b/tests/migrations/test_migrations_no_default/0001_initial.py new file mode 100644 index 0000000000..c30910e056 --- /dev/null +++ b/tests/migrations/test_migrations_no_default/0001_initial.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SillyModel', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('silly_field', models.BooleanField(default=False)), + ], + options={ + }, + bases=(models.Model,), + ), + ] diff --git a/tests/migrations/test_migrations_no_default/__init__.py b/tests/migrations/test_migrations_no_default/__init__.py new file mode 100644 index 0000000000..e69de29bb2