From b909873d779af069e12a0ee58d5851e76328fc44 Mon Sep 17 00:00:00 2001 From: Eric Sherman Date: Thu, 19 Oct 2023 11:22:20 -0400 Subject: [PATCH 1/2] added new flag and test for showmigrations --- .../management/commands/showmigrations.py | 20 ++++++++++++---- tests/migrations/test_commands.py | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/django/core/management/commands/showmigrations.py b/django/core/management/commands/showmigrations.py index 203f92151d21..bfe5e1946d46 100644 --- a/django/core/management/commands/showmigrations.py +++ b/django/core/management/commands/showmigrations.py @@ -24,6 +24,14 @@ def add_arguments(self, parser): '"default" database.' ), ) + parser.add_argument( + "--unapplied", + "-u", + action="store_true", + help=( + "Like `--list`, but only shows unapplied migrations" + ), + ) formats = parser.add_mutually_exclusive_group() formats.add_argument( @@ -55,7 +63,7 @@ def add_arguments(self, parser): def handle(self, *args, **options): self.verbosity = options["verbosity"] - + self.show_unapplied_only = options["unapplied"] # Get the database we're operating from db = options["database"] connection = connections[db] @@ -63,7 +71,10 @@ def handle(self, *args, **options): if options["format"] == "plan": return self.show_plan(connection, options["app_label"]) else: - return self.show_list(connection, options["app_label"]) + return self.show_list(connection, + options["app_label"], + show_unapplied_only=self.show_unapplied_only, + ) def _validate_app_names(self, loader, app_names): has_bad_names = False @@ -76,7 +87,7 @@ def _validate_app_names(self, loader, app_names): if has_bad_names: sys.exit(2) - def show_list(self, connection, app_names=None): + def show_list(self, connection, app_names=None, show_unapplied_only=False): """ Show a list of all migrations on the system, or only those of some named apps. @@ -123,7 +134,8 @@ def show_list(self, connection, app_names=None): "%Y-%m-%d %H:%M:%S" ) ) - self.stdout.write(output) + if not show_unapplied_only: + self.stdout.write(output) else: self.stdout.write(" [ ] %s" % title) shown.add(plan_node) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 387cef924af3..5610fd6ec512 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -456,6 +456,29 @@ def test_showmigrations_list(self): # Cleanup by unmigrating everything call_command("migrate", "migrations", "zero", verbosity=0) + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) + def test_showmigrations_show_unapplied_only(self): + """ + showmigrations --unapplied only displays unapplied migrations + """ + out = io.StringIO() + + call_command("migrate", "migrations", "0001", verbosity=0) + + # Giving the explicit app_label tests for selective `show_list` in the command + call_command( + "showmigrations", + "migrations", + unapplied=True, + format="list", + stdout=out, + verbosity=0, + no_color=True, + ) + self.assertEqual("migrations\n [ ] 0002_second\n", out.getvalue().lower()) + # Cleanup by unmigrating everything + call_command("migrate", "migrations", "zero", verbosity=0) + @override_settings( MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"} ) From fdb557898f830c6c1d9c23d93423347e1bb3b630 Mon Sep 17 00:00:00 2001 From: Eric Sherman Date: Fri, 20 Oct 2023 11:23:41 -0400 Subject: [PATCH 2/2] handle case for multiple app labels --- .../management/commands/showmigrations.py | 13 +++++++++--- tests/migrations/test_commands.py | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/django/core/management/commands/showmigrations.py b/django/core/management/commands/showmigrations.py index bfe5e1946d46..6f9c6e8bf67c 100644 --- a/django/core/management/commands/showmigrations.py +++ b/django/core/management/commands/showmigrations.py @@ -106,8 +106,8 @@ def show_list(self, connection, app_names=None, show_unapplied_only=False): # For each app, print its migrations in order from oldest (roots) to # newest (leaves). for app_name in app_names: - self.stdout.write(app_name, self.style.MIGRATE_LABEL) shown = set() + outputs = [] for node in graph.leaf_nodes(app_name): for plan_node in graph.forwards_plan(node): if plan_node not in shown and plan_node[0] == app_name: @@ -135,12 +135,19 @@ def show_list(self, connection, app_names=None, show_unapplied_only=False): ) ) if not show_unapplied_only: - self.stdout.write(output) + outputs.append(output) else: - self.stdout.write(" [ ] %s" % title) + output = " [ ] %s" % title + outputs.append(output) shown.add(plan_node) + if outputs: + self.stdout.write(app_name, self.style.MIGRATE_LABEL) + for output in outputs: + self.stdout.write(output) + # If we didn't print anything, then a small message if not shown: + self.stdout.write(app_name, self.style.MIGRATE_LABEL) self.stdout.write(" (no migrations)", self.style.ERROR) def show_plan(self, connection, app_names=None): diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 5610fd6ec512..ed3bae3f79af 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -479,6 +479,26 @@ def test_showmigrations_show_unapplied_only(self): # Cleanup by unmigrating everything call_command("migrate", "migrations", "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_unapplied_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 = io.StringIO() + + # run all migrations for author app + call_command("migrate", "author_app", verbosity=0) + call_command("showmigrations", unapplied=True, format="list", stdout=out) + self.assertNotIn("author_app", out.getvalue()) + @override_settings( MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"} ) @@ -843,6 +863,7 @@ def test_showmigrations_plan_multiple_app_labels(self): call_command( "showmigrations", "author_app", "mutate_state_b", format="plan", stdout=out ) + self.assertEqual( "[ ] author_app.0001_initial\n" "[ ] book_app.0001_initial\n"