diff --git a/django/core/management/commands/showmigrations.py b/django/core/management/commands/showmigrations.py index 0722af7362..48bea1f3f0 100644 --- a/django/core/management/commands/showmigrations.py +++ b/django/core/management/commands/showmigrations.py @@ -22,7 +22,11 @@ class Command(BaseCommand): formats = parser.add_mutually_exclusive_group() formats.add_argument( '--list', '-l', action='store_const', dest='format', const='list', - help='Shows a list of all migrations and which are applied.', + help=( + 'Shows a list of all migrations and which are applied. ' + 'With a verbosity level of 2 or above, the applied datetimes ' + 'will be included.' + ), ) formats.add_argument( '--plan', '-p', action='store_const', dest='format', const='plan', @@ -84,9 +88,13 @@ class Command(BaseCommand): title = plan_node[1] if graph.nodes[plan_node].replaces: title += " (%s squashed migrations)" % len(graph.nodes[plan_node].replaces) + applied_migration = loader.applied_migrations.get(plan_node) # Mark it as applied/unapplied - if plan_node in loader.applied_migrations: - self.stdout.write(" [X] %s" % title) + if applied_migration: + output = ' [X] %s' % title + if self.verbosity >= 2: + output += ' (applied at %s)' % applied_migration.applied.strftime('%Y-%m-%d %H:%M:%S') + self.stdout.write(output) else: self.stdout.write(" [ ] %s" % title) shown.add(plan_node) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 63467ae750..d52e827f18 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1079,13 +1079,18 @@ Shows all migrations in a project. You can choose from one of two formats: Lists all of the apps Django knows about, the migrations available for each app, and whether or not each migration is applied (marked by an ``[X]`` next to -the migration name). +the migration name). For a ``--verbosity`` of 2 and above, the applied +datetimes are also shown. Apps without migrations are also listed, but have ``(no migrations)`` printed under them. This is the default output format. +.. versionchanged:: 3.0 + + Output of the applied datetimes at verbosity 2 and above was added. + .. django-admin-option:: --plan, -p Shows the migration plan Django will follow to apply migrations. Like diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 1d4df533bd..376deab359 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -154,6 +154,9 @@ Management Commands * The new :option:`compilemessages --ignore` option allows ignoring specific directories when searching for ``.po`` files to compile. +* :option:`showmigrations --list` now shows the applied datetimes when + ``--verbosity`` is 2 and above. + Migrations ~~~~~~~~~~ diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 57e71f4643..a15ae461ce 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -250,6 +250,16 @@ class MigrateTests(MigrationTestBase): ' [ ] 0002_second\n', out.getvalue().lower() ) + out = io.StringIO() + # Applied datetimes are displayed at verbosity 2+. + call_command('showmigrations', 'migrations', stdout=out, verbosity=2, no_color=True) + migration1 = MigrationRecorder(connection).migration_qs.get(app='migrations', name='0001_initial') + self.assertEqual( + 'migrations\n' + ' [x] 0001_initial (applied at %s)\n' + ' [ ] 0002_second\n' % migration1.applied.strftime('%Y-%m-%d %H:%M:%S'), + out.getvalue().lower() + ) # Cleanup by unmigrating everything call_command("migrate", "migrations", "zero", verbosity=0)