Fixed #27432 -- Made app_label arguments limit showmigrations --plan output.
This commit is contained in:
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
|
scott@staplefish.com
|
||||||
Sean Brant
|
Sean Brant
|
||||||
Sebastian Hillig <sebastian.hillig@gmail.com>
|
Sebastian Hillig <sebastian.hillig@gmail.com>
|
||||||
|
Sebastian Spiegel <http://www.tivix.com/>
|
||||||
Selwin Ong <selwin@ui.co.id>
|
Selwin Ong <selwin@ui.co.id>
|
||||||
Sengtha Chay <sengtha@e-khmer.com>
|
Sengtha Chay <sengtha@e-khmer.com>
|
||||||
Senko Rašić <senko.rasic@dobarkod.hr>
|
Senko Rašić <senko.rasic@dobarkod.hr>
|
||||||
|
|
|
@ -43,10 +43,18 @@ class Command(BaseCommand):
|
||||||
connection = connections[db]
|
connection = connections[db]
|
||||||
|
|
||||||
if options['format'] == "plan":
|
if options['format'] == "plan":
|
||||||
return self.show_plan(connection)
|
return self.show_plan(connection, options['app_label'])
|
||||||
else:
|
else:
|
||||||
return self.show_list(connection, options['app_label'])
|
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):
|
def show_list(self, connection, app_names=None):
|
||||||
"""
|
"""
|
||||||
Shows a list of all migrations on the system, or only those of
|
Shows a list of all migrations on the system, or only those of
|
||||||
|
@ -57,12 +65,7 @@ class Command(BaseCommand):
|
||||||
graph = loader.graph
|
graph = loader.graph
|
||||||
# If we were passed a list of apps, validate it
|
# If we were passed a list of apps, validate it
|
||||||
if app_names:
|
if app_names:
|
||||||
invalid_apps = []
|
self._validate_app_names(loader, app_names)
|
||||||
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)))
|
|
||||||
# Otherwise, show all apps in alphabetic order
|
# Otherwise, show all apps in alphabetic order
|
||||||
else:
|
else:
|
||||||
app_names = sorted(loader.migrated_apps)
|
app_names = sorted(loader.migrated_apps)
|
||||||
|
@ -88,13 +91,18 @@ class Command(BaseCommand):
|
||||||
if not shown:
|
if not shown:
|
||||||
self.stdout.write(" (no migrations)", self.style.ERROR)
|
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
|
# Load migrations from disk/DB
|
||||||
loader = MigrationLoader(connection)
|
loader = MigrationLoader(connection)
|
||||||
graph = loader.graph
|
graph = loader.graph
|
||||||
|
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()
|
targets = graph.leaf_nodes()
|
||||||
plan = []
|
plan = []
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|
|
@ -1015,11 +1015,17 @@ This is the default output format.
|
||||||
|
|
||||||
.. django-admin-option:: --plan, -p
|
.. django-admin-option:: --plan, -p
|
||||||
|
|
||||||
Shows the migration plan Django will follow to apply migrations. Any supplied
|
Shows the migration plan Django will follow to apply migrations. Like
|
||||||
app labels are ignored because the plan might go beyond those apps. Like
|
|
||||||
``--list``, applied migrations are marked by an ``[X]``. For a ``--verbosity``
|
``--list``, applied migrations are marked by an ``[X]``. For a ``--verbosity``
|
||||||
of 2 and above, all dependencies of a migration will also be shown.
|
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
|
.. django-admin-option:: --database DATABASE
|
||||||
|
|
||||||
Specifies the database to examine. Defaults to ``default``.
|
Specifies the database to examine. Defaults to ``default``.
|
||||||
|
|
|
@ -325,6 +325,9 @@ Management Commands
|
||||||
* The new :option:`diffsettings --default` option allows specifying a settings
|
* The new :option:`diffsettings --default` option allows specifying a settings
|
||||||
module other than Django's default settings to compare against.
|
module other than Django's default settings to compare against.
|
||||||
|
|
||||||
|
* ``app_label``\s arguments now limit the :option:`showmigrations --plan`
|
||||||
|
output.
|
||||||
|
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -339,6 +339,97 @@ class MigrateTests(MigrationTestBase):
|
||||||
out.getvalue().lower()
|
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"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
||||||
def test_sqlmigrate_forwards(self):
|
def test_sqlmigrate_forwards(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue