mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #27432 -- Made app_label arguments limit showmigrations --plan output.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							d976760260
						
					
				
				
					commit
					8b734d2f99
				
			
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -685,6 +685,7 @@ answer newbie questions, and generally made Django that much better: | ||||
|     scott@staplefish.com | ||||
|     Sean Brant | ||||
|     Sebastian Hillig <sebastian.hillig@gmail.com> | ||||
|     Sebastian Spiegel <http://www.tivix.com/> | ||||
|     Selwin Ong <selwin@ui.co.id> | ||||
|     Sengtha Chay <sengtha@e-khmer.com> | ||||
|     Senko Rašić <senko.rasic@dobarkod.hr> | ||||
|   | ||||
| @@ -43,10 +43,18 @@ class Command(BaseCommand): | ||||
|         connection = connections[db] | ||||
|  | ||||
|         if options['format'] == "plan": | ||||
|             return self.show_plan(connection) | ||||
|             return self.show_plan(connection, options['app_label']) | ||||
|         else: | ||||
|             return self.show_list(connection, options['app_label']) | ||||
|  | ||||
|     def _validate_app_names(self, loader, app_names): | ||||
|         invalid_apps = [] | ||||
|         for app_name in app_names: | ||||
|             if app_name not in loader.migrated_apps: | ||||
|                 invalid_apps.append(app_name) | ||||
|         if invalid_apps: | ||||
|             raise CommandError('No migrations present for: %s' % (', '.join(sorted(invalid_apps)))) | ||||
|  | ||||
|     def show_list(self, connection, app_names=None): | ||||
|         """ | ||||
|         Shows a list of all migrations on the system, or only those of | ||||
| @@ -57,12 +65,7 @@ class Command(BaseCommand): | ||||
|         graph = loader.graph | ||||
|         # If we were passed a list of apps, validate it | ||||
|         if app_names: | ||||
|             invalid_apps = [] | ||||
|             for app_name in app_names: | ||||
|                 if app_name not in loader.migrated_apps: | ||||
|                     invalid_apps.append(app_name) | ||||
|             if invalid_apps: | ||||
|                 raise CommandError("No migrations present for: %s" % (", ".join(invalid_apps))) | ||||
|             self._validate_app_names(loader, app_names) | ||||
|         # Otherwise, show all apps in alphabetic order | ||||
|         else: | ||||
|             app_names = sorted(loader.migrated_apps) | ||||
| @@ -88,14 +91,19 @@ class Command(BaseCommand): | ||||
|             if not shown: | ||||
|                 self.stdout.write(" (no migrations)", self.style.ERROR) | ||||
|  | ||||
|     def show_plan(self, connection): | ||||
|     def show_plan(self, connection, app_names=None): | ||||
|         """ | ||||
|         Shows all known migrations in the order they will be applied | ||||
|         Shows all known migrations (or only those of the specified app_names) | ||||
|         in the order they will be applied. | ||||
|         """ | ||||
|         # Load migrations from disk/DB | ||||
|         loader = MigrationLoader(connection) | ||||
|         graph = loader.graph | ||||
|         targets = graph.leaf_nodes() | ||||
|         if app_names: | ||||
|             self._validate_app_names(loader, app_names) | ||||
|             targets = [key for key in graph.leaf_nodes() if key[0] in app_names] | ||||
|         else: | ||||
|             targets = graph.leaf_nodes() | ||||
|         plan = [] | ||||
|         seen = set() | ||||
|  | ||||
|   | ||||
| @@ -1015,11 +1015,17 @@ This is the default output format. | ||||
|  | ||||
| .. django-admin-option:: --plan, -p | ||||
|  | ||||
| Shows the migration plan Django will follow to apply migrations. Any supplied | ||||
| app labels are ignored because the plan might go beyond those apps. Like | ||||
| Shows the migration plan Django will follow to apply migrations. Like | ||||
| ``--list``, applied migrations are marked by an ``[X]``. For a ``--verbosity`` | ||||
| of 2 and above, all dependencies of a migration will also be shown. | ||||
|  | ||||
| ``app_label``\s arguments limit the output, however, dependencies of provided | ||||
| apps may also be included. | ||||
|  | ||||
| .. versionchanged:: 1.11 | ||||
|  | ||||
|     In older versions, ``showmigrations --plan`` ignores app labels. | ||||
|  | ||||
| .. django-admin-option:: --database DATABASE | ||||
|  | ||||
| Specifies the database to examine. Defaults to ``default``. | ||||
|   | ||||
| @@ -325,6 +325,9 @@ Management Commands | ||||
| * The new :option:`diffsettings --default` option allows specifying a settings | ||||
|   module other than Django's default settings to compare against. | ||||
|  | ||||
| * ``app_label``\s arguments now limit the :option:`showmigrations --plan` | ||||
|   output. | ||||
|  | ||||
| Migrations | ||||
| ~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -339,6 +339,97 @@ class MigrateTests(MigrationTestBase): | ||||
|             out.getvalue().lower() | ||||
|         ) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=[ | ||||
|         'migrations.migrations_test_apps.mutate_state_b', | ||||
|         'migrations.migrations_test_apps.alter_fk.author_app', | ||||
|         'migrations.migrations_test_apps.alter_fk.book_app', | ||||
|     ]) | ||||
|     def test_showmigrations_plan_single_app_label(self): | ||||
|         """ | ||||
|         `showmigrations --plan app_label` output with a single app_label. | ||||
|         """ | ||||
|         # Single app with no dependencies on other apps. | ||||
|         out = six.StringIO() | ||||
|         call_command('showmigrations', 'mutate_state_b', format='plan', stdout=out) | ||||
|         self.assertEqual( | ||||
|             '[ ]  mutate_state_b.0001_initial\n' | ||||
|             '[ ]  mutate_state_b.0002_add_field\n', | ||||
|             out.getvalue() | ||||
|         ) | ||||
|         # Single app with dependencies. | ||||
|         out = six.StringIO() | ||||
|         call_command('showmigrations', 'author_app', format='plan', stdout=out) | ||||
|         self.assertEqual( | ||||
|             '[ ]  author_app.0001_initial\n' | ||||
|             '[ ]  book_app.0001_initial\n' | ||||
|             '[ ]  author_app.0002_alter_id\n', | ||||
|             out.getvalue() | ||||
|         ) | ||||
|         # Some migrations already applied. | ||||
|         call_command('migrate', 'author_app', '0001', verbosity=0) | ||||
|         out = six.StringIO() | ||||
|         call_command('showmigrations', 'author_app', format='plan', stdout=out) | ||||
|         self.assertEqual( | ||||
|             '[X]  author_app.0001_initial\n' | ||||
|             '[ ]  book_app.0001_initial\n' | ||||
|             '[ ]  author_app.0002_alter_id\n', | ||||
|             out.getvalue() | ||||
|         ) | ||||
|         # Cleanup by unmigrating author_app. | ||||
|         call_command('migrate', 'author_app', 'zero', verbosity=0) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=[ | ||||
|         'migrations.migrations_test_apps.mutate_state_b', | ||||
|         'migrations.migrations_test_apps.alter_fk.author_app', | ||||
|         'migrations.migrations_test_apps.alter_fk.book_app', | ||||
|     ]) | ||||
|     def test_showmigrations_plan_multiple_app_labels(self): | ||||
|         """ | ||||
|         `showmigrations --plan app_label` output with multiple app_labels. | ||||
|         """ | ||||
|         # Multiple apps: author_app depends on book_app; mutate_state_b doesn't | ||||
|         # depend on other apps. | ||||
|         out = six.StringIO() | ||||
|         call_command('showmigrations', 'mutate_state_b', 'author_app', format='plan', stdout=out) | ||||
|         self.assertEqual( | ||||
|             '[ ]  author_app.0001_initial\n' | ||||
|             '[ ]  book_app.0001_initial\n' | ||||
|             '[ ]  author_app.0002_alter_id\n' | ||||
|             '[ ]  mutate_state_b.0001_initial\n' | ||||
|             '[ ]  mutate_state_b.0002_add_field\n', | ||||
|             out.getvalue() | ||||
|         ) | ||||
|         # Multiple apps: args order shouldn't matter (the same result is | ||||
|         # expected as above). | ||||
|         out = six.StringIO() | ||||
|         call_command('showmigrations', 'author_app', 'mutate_state_b', format='plan', stdout=out) | ||||
|         self.assertEqual( | ||||
|             '[ ]  author_app.0001_initial\n' | ||||
|             '[ ]  book_app.0001_initial\n' | ||||
|             '[ ]  author_app.0002_alter_id\n' | ||||
|             '[ ]  mutate_state_b.0001_initial\n' | ||||
|             '[ ]  mutate_state_b.0002_add_field\n', | ||||
|             out.getvalue() | ||||
|         ) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=['migrations.migrations_test_apps.unmigrated_app']) | ||||
|     def test_showmigrations_plan_app_label_error(self): | ||||
|         """ | ||||
|         `showmigrations --plan app_label` raises an error when no app or | ||||
|         no migrations are present in provided app labels. | ||||
|         """ | ||||
|         # App with no migrations. | ||||
|         with self.assertRaisesMessage(CommandError, 'No migrations present for: unmigrated_app'): | ||||
|             call_command('showmigrations', 'unmigrated_app', format='plan') | ||||
|         # Nonexistent app (wrong app label). | ||||
|         with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app'): | ||||
|             call_command('showmigrations', 'nonexistent_app', format='plan') | ||||
|         # Multiple nonexistent apps; input order shouldn't matter. | ||||
|         with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app1, nonexistent_app2'): | ||||
|             call_command('showmigrations', 'nonexistent_app1', 'nonexistent_app2', format='plan') | ||||
|         with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app1, nonexistent_app2'): | ||||
|             call_command('showmigrations', 'nonexistent_app2', 'nonexistent_app1', format='plan') | ||||
|  | ||||
|     @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) | ||||
|     def test_sqlmigrate_forwards(self): | ||||
|         """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user