mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #29026 -- Added --scriptable option to makemigrations.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							274771df91
						
					
				
				
					commit
					6f78cb6b13
				
			| @@ -57,9 +57,20 @@ class Command(BaseCommand): | ||||
|             '--check', action='store_true', dest='check_changes', | ||||
|             help='Exit with a non-zero status if model changes are missing migrations.', | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--scriptable', action='store_true', dest='scriptable', | ||||
|             help=( | ||||
|                 'Divert log output and input prompts to stderr, writing only ' | ||||
|                 'paths of generated migration files to stdout.' | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def log_output(self): | ||||
|         return self.stderr if self.scriptable else self.stdout | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.stdout.write(msg) | ||||
|         self.log_output.write(msg) | ||||
|  | ||||
|     @no_translations | ||||
|     def handle(self, *app_labels, **options): | ||||
| @@ -73,6 +84,10 @@ class Command(BaseCommand): | ||||
|             raise CommandError('The migration name must be a valid Python identifier.') | ||||
|         self.include_header = options['include_header'] | ||||
|         check_changes = options['check_changes'] | ||||
|         self.scriptable = options['scriptable'] | ||||
|         # If logs and prompts are diverted to stderr, remove the ERROR style. | ||||
|         if self.scriptable: | ||||
|             self.stderr.style_func = None | ||||
|  | ||||
|         # Make sure the app they asked for exists | ||||
|         app_labels = set(app_labels) | ||||
| @@ -147,7 +162,7 @@ class Command(BaseCommand): | ||||
|             questioner = InteractiveMigrationQuestioner( | ||||
|                 specified_apps=app_labels, | ||||
|                 dry_run=self.dry_run, | ||||
|                 prompt_output=self.stdout, | ||||
|                 prompt_output=self.log_output, | ||||
|             ) | ||||
|         else: | ||||
|             questioner = NonInteractiveMigrationQuestioner( | ||||
| @@ -226,6 +241,8 @@ class Command(BaseCommand): | ||||
|                     self.log('  %s\n' % self.style.MIGRATE_LABEL(migration_string)) | ||||
|                     for operation in migration.operations: | ||||
|                         self.log('    - %s' % operation.describe()) | ||||
|                     if self.scriptable: | ||||
|                         self.stdout.write(migration_string) | ||||
|                 if not self.dry_run: | ||||
|                     # Write the migrations file to the disk. | ||||
|                     migrations_directory = os.path.dirname(writer.path) | ||||
| @@ -254,7 +271,7 @@ class Command(BaseCommand): | ||||
|         if it's safe; otherwise, advises on how to fix it. | ||||
|         """ | ||||
|         if self.interactive: | ||||
|             questioner = InteractiveMigrationQuestioner(prompt_output=self.stdout) | ||||
|             questioner = InteractiveMigrationQuestioner(prompt_output=self.log_output) | ||||
|         else: | ||||
|             questioner = MigrationQuestioner(defaults={'ask_merge': True}) | ||||
|  | ||||
| @@ -327,6 +344,8 @@ class Command(BaseCommand): | ||||
|                         fh.write(writer.as_string()) | ||||
|                     if self.verbosity > 0: | ||||
|                         self.log('\nCreated new merge migration %s' % writer.path) | ||||
|                         if self.scriptable: | ||||
|                             self.stdout.write(writer.path) | ||||
|                 elif self.verbosity == 3: | ||||
|                     # Alternatively, makemigrations --merge --dry-run --verbosity 3 | ||||
|                     # will log the merge migrations rather than saving the file | ||||
|   | ||||
| @@ -825,6 +825,13 @@ Generate migration files without Django version and timestamp header. | ||||
| Makes ``makemigrations`` exit with a non-zero status when model changes without | ||||
| migrations are detected. | ||||
|  | ||||
| .. django-admin-option:: --scriptable | ||||
|  | ||||
| .. versionadded:: 4.1 | ||||
|  | ||||
| Diverts log output and input prompts to ``stderr``, writing only paths of | ||||
| generated migration files to ``stdout``. | ||||
|  | ||||
| ``migrate`` | ||||
| ----------- | ||||
|  | ||||
|   | ||||
| @@ -210,6 +210,10 @@ Management Commands | ||||
| * :option:`makemigrations --no-input` now logs default answers and reasons why | ||||
|   migrations cannot be created. | ||||
|  | ||||
| * The new :option:`makemigrations --scriptable` options diverts log output and | ||||
|   input prompts to ``stderr``, writing only paths of generated migration files | ||||
|   to ``stdout``. | ||||
|  | ||||
| Migrations | ||||
| ~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -1667,6 +1667,47 @@ class MakeMigrationsTests(MigrationTestBase): | ||||
|         self.assertIn("model_name='sillymodel',", out.getvalue()) | ||||
|         self.assertIn("name='silly_char',", out.getvalue()) | ||||
|  | ||||
|     def test_makemigrations_scriptable(self): | ||||
|         """ | ||||
|         With scriptable=True, log output is diverted to stderr, and only the | ||||
|         paths of generated migration files are written to stdout. | ||||
|         """ | ||||
|         out = io.StringIO() | ||||
|         err = io.StringIO() | ||||
|         with self.temporary_migration_module( | ||||
|             module='migrations.migrations.test_migrations', | ||||
|         ) as migration_dir: | ||||
|             call_command( | ||||
|                 'makemigrations', | ||||
|                 'migrations', | ||||
|                 scriptable=True, | ||||
|                 stdout=out, | ||||
|                 stderr=err, | ||||
|             ) | ||||
|         initial_file = os.path.join(migration_dir, '0001_initial.py') | ||||
|         self.assertEqual(out.getvalue(), f'{initial_file}\n') | ||||
|         self.assertIn('    - Create model ModelWithCustomBase\n', err.getvalue()) | ||||
|  | ||||
|     @mock.patch('builtins.input', return_value='Y') | ||||
|     def test_makemigrations_scriptable_merge(self, mock_input): | ||||
|         out = io.StringIO() | ||||
|         err = io.StringIO() | ||||
|         with self.temporary_migration_module( | ||||
|             module='migrations.test_migrations_conflict', | ||||
|         ) as migration_dir: | ||||
|             call_command( | ||||
|                 'makemigrations', | ||||
|                 'migrations', | ||||
|                 merge=True, | ||||
|                 name='merge', | ||||
|                 scriptable=True, | ||||
|                 stdout=out, | ||||
|                 stderr=err, | ||||
|             ) | ||||
|         merge_file = os.path.join(migration_dir, '0003_merge.py') | ||||
|         self.assertEqual(out.getvalue(), f'{merge_file}\n') | ||||
|         self.assertIn(f'Created new merge migration {merge_file}', err.getvalue()) | ||||
|  | ||||
|     def test_makemigrations_migrations_modules_path_not_exist(self): | ||||
|         """ | ||||
|         makemigrations creates migrations when specifying a custom location | ||||
|   | ||||
		Reference in New Issue
	
	Block a user