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( |         autodetector = MigrationAutodetector( | ||||||
|             loader.project_state(), |             loader.project_state(), | ||||||
|             ProjectState.from_apps(apps), |             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 |         # 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. |     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.defaults = defaults or {} | ||||||
|         self.specified_apps = specified_apps or set() |         self.specified_apps = specified_apps or set() | ||||||
|  |         self.dry_run = dry_run | ||||||
|  |  | ||||||
|     def ask_initial(self, app_label): |     def ask_initial(self, app_label): | ||||||
|         "Should we create an initial migration for the app?" |         "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): |     def ask_not_null_addition(self, field_name, model_name): | ||||||
|         "Adding a NOT NULL field to a model" |         "Adding a NOT NULL field to a model" | ||||||
|         choice = self._choice_input( |         if not self.dry_run: | ||||||
|             "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) + |             choice = self._choice_input( | ||||||
|             "we can't do that (the database needs something to populate existing rows).\n" + |                 "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) + | ||||||
|             "Please select a fix:", |                 "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", |                     "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) |             if choice == 2: | ||||||
|         else: |                 sys.exit(3) | ||||||
|             print("Please enter the default value now, as valid Python") |             else: | ||||||
|             print("The datetime module is available, so you can do e.g. datetime.date.today()") |                 print("Please enter the default value now, as valid Python") | ||||||
|             while True: |                 print("The datetime module is available, so you can do e.g. datetime.date.today()") | ||||||
|                 if six.PY3: |                 while True: | ||||||
|                     # Six does not correctly abstract over the fact that |                     if six.PY3: | ||||||
|                     # py3 input returns a unicode string, while py2 raw_input |                         # Six does not correctly abstract over the fact that | ||||||
|                     # returns a bytestring. |                         # py3 input returns a unicode string, while py2 raw_input | ||||||
|                     code = input(">>> ") |                         # returns a bytestring. | ||||||
|                 else: |                         code = input(">>> ") | ||||||
|                     code = input(">>> ").decode(sys.stdin.encoding) |                     else: | ||||||
|                 if not code: |                         code = input(">>> ").decode(sys.stdin.encoding) | ||||||
|                     print("Please enter some code, or 'exit' (with no quotes) to exit.") |                     if not code: | ||||||
|                 elif code == "exit": |                         print("Please enter some code, or 'exit' (with no quotes) to exit.") | ||||||
|                     sys.exit(1) |                     elif code == "exit": | ||||||
|                 else: |                         sys.exit(1) | ||||||
|                     try: |                     else: | ||||||
|                         return eval(code, {}, {"datetime": datetime_safe}) |                         try: | ||||||
|                     except (SyntaxError, NameError) as e: |                             return eval(code, {}, {"datetime": datetime_safe}) | ||||||
|                         print("Invalid input: %s" % e) |                         except (SyntaxError, NameError) as e: | ||||||
|  |                             print("Invalid input: %s" % e) | ||||||
|  |         return None | ||||||
|  |  | ||||||
|     def ask_rename(self, model_name, old_name, new_name, field_instance): |     def ask_rename(self, model_name, old_name, new_name, field_instance): | ||||||
|         "Was this field really renamed?" |         "Was this field really renamed?" | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import os | |||||||
| import shutil | import shutil | ||||||
|  |  | ||||||
| from django.apps import apps | from django.apps import apps | ||||||
|  | from django.db import models | ||||||
| from django.core.management import call_command, CommandError | from django.core.management import call_command, CommandError | ||||||
| from django.db.migrations import questioner | from django.db.migrations import questioner | ||||||
| from django.test import override_settings, override_system_checks | from django.test import override_settings, override_system_checks | ||||||
| @@ -362,3 +363,22 @@ class MakeMigrationsTests(MigrationTestBase): | |||||||
|         self.assertIn("Merging migrations", stdout.getvalue()) |         self.assertIn("Merging migrations", stdout.getvalue()) | ||||||
|         self.assertIn("Branch 0002_second", stdout.getvalue()) |         self.assertIn("Branch 0002_second", stdout.getvalue()) | ||||||
|         self.assertIn("Branch 0002_conflicting_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