mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[1.7.x] 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.
Backport of ee14961a2a from master
			
			
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							be733bf672
						
					
				
				
					commit
					a59870e217
				
			| @@ -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 | ||||
|   | ||||
| @@ -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?" | ||||
|   | ||||
| @@ -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()) | ||||
|   | ||||
							
								
								
									
										23
									
								
								tests/migrations/test_migrations_no_default/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/migrations/test_migrations_no_default/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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,), | ||||
|         ), | ||||
|     ] | ||||
		Reference in New Issue
	
	Block a user