diff --git a/AUTHORS b/AUTHORS index 8bfab4fe02..0c6994b5ea 100644 --- a/AUTHORS +++ b/AUTHORS @@ -449,6 +449,7 @@ answer newbie questions, and generally made Django that much better: Julia Matsieva Julian Bez Julien Phalip + Junyoung Choi junzhang.jn@gmail.com Jure Cuhalev Justin Bronn diff --git a/django/core/management/commands/showmigrations.py b/django/core/management/commands/showmigrations.py index abac283160..4c86ba1a17 100644 --- a/django/core/management/commands/showmigrations.py +++ b/django/core/management/commands/showmigrations.py @@ -1,4 +1,7 @@ -from django.core.management.base import BaseCommand, CommandError +import sys + +from django.apps import apps +from django.core.management.base import BaseCommand from django.db import DEFAULT_DB_ALIAS, connections from django.db.migrations.loader import MigrationLoader @@ -45,12 +48,15 @@ class Command(BaseCommand): return self.show_list(connection, options['app_label']) def _validate_app_names(self, loader, app_names): - invalid_apps = [] + has_bad_names = False 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)))) + try: + apps.get_app_config(app_name) + except LookupError as err: + self.stderr.write(str(err)) + has_bad_names = True + if has_bad_names: + sys.exit(2) def show_list(self, connection, app_names=None): """ @@ -129,3 +135,5 @@ class Command(BaseCommand): self.stdout.write("[X] %s.%s%s" % (node.key[0], node.key[1], deps)) else: self.stdout.write("[ ] %s.%s%s" % (node.key[0], node.key[1], deps)) + if not plan: + self.stdout.write('(no migrations)', self.style.ERROR) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 816abea65a..63e8615e67 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -381,8 +381,9 @@ class MigrateTests(MigrationTestBase): @override_settings(INSTALLED_APPS=['migrations.migrations_test_apps.unmigrated_app']) def test_showmigrations_unmigrated_app(self): - with self.assertRaisesMessage(CommandError, 'No migrations present for: unmigrated_app'): - call_command('showmigrations', 'unmigrated_app') + out = io.StringIO() + call_command('showmigrations', 'unmigrated_app', stdout=out, no_color=True) + self.assertEqual('unmigrated_app\n (no migrations)\n', out.getvalue().lower()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_empty"}) def test_showmigrations_plan_no_migrations(self): @@ -390,12 +391,12 @@ class MigrateTests(MigrationTestBase): Tests --plan output of showmigrations command without migrations """ out = io.StringIO() - call_command("showmigrations", format='plan', stdout=out) - self.assertEqual("", out.getvalue().lower()) + call_command('showmigrations', format='plan', stdout=out, no_color=True) + self.assertEqual('(no migrations)\n', out.getvalue().lower()) out = io.StringIO() - call_command("showmigrations", format='plan', stdout=out, verbosity=2) - self.assertEqual("", out.getvalue().lower()) + call_command('showmigrations', format='plan', stdout=out, verbosity=2, no_color=True) + self.assertEqual('(no migrations)\n', out.getvalue().lower()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_complex"}) def test_showmigrations_plan_squashed(self): @@ -522,22 +523,10 @@ class MigrateTests(MigrationTestBase): ) @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') + def test_showmigrations_plan_app_label_no_migrations(self): + out = io.StringIO() + call_command('showmigrations', 'unmigrated_app', format='plan', stdout=out, no_color=True) + self.assertEqual('(no migrations)\n', out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) def test_sqlmigrate_forwards(self): @@ -1561,6 +1550,18 @@ class AppLabelErrorTests(TestCase): with self.assertRaisesMessage(CommandError, self.did_you_mean_auth_error): call_command('migrate', 'django.contrib.auth') + def test_showmigrations_nonexistent_app_label(self): + err = io.StringIO() + with self.assertRaises(SystemExit): + call_command('showmigrations', 'nonexistent_app', stderr=err) + self.assertIn(self.nonexistent_app_error, err.getvalue()) + + def test_showmigrations_app_name_specified_as_label(self): + err = io.StringIO() + with self.assertRaises(SystemExit): + call_command('showmigrations', 'django.contrib.auth', stderr=err) + self.assertIn(self.did_you_mean_auth_error, err.getvalue()) + def test_sqlmigrate_nonexistent_app_label(self): with self.assertRaisesMessage(CommandError, self.nonexistent_app_error): call_command('sqlmigrate', 'nonexistent_app', '0002')