2014-09-03 16:51:07 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.db import DEFAULT_DB_ALIAS, connections
|
2014-09-03 16:51:07 +08:00
|
|
|
from django.db.migrations.loader import MigrationLoader
|
|
|
|
|
|
|
|
|
|
|
|
class Command(BaseCommand):
|
|
|
|
help = "Shows all available migrations for the current project"
|
|
|
|
|
|
|
|
def add_arguments(self, parser):
|
2016-03-29 06:33:29 +08:00
|
|
|
parser.add_argument(
|
|
|
|
'app_label', nargs='*',
|
|
|
|
help='App labels of applications to limit the output to.',
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
'--database', action='store', dest='database', default=DEFAULT_DB_ALIAS,
|
|
|
|
help='Nominates a database to synchronize. Defaults to the "default" database.',
|
|
|
|
)
|
2014-09-03 16:51:07 +08:00
|
|
|
|
|
|
|
formats = parser.add_mutually_exclusive_group()
|
2016-03-29 06:33:29 +08:00
|
|
|
formats.add_argument(
|
|
|
|
'--list', '-l', action='store_const', dest='format', const='list',
|
|
|
|
help='Shows a list of all migrations and which are applied.',
|
|
|
|
)
|
|
|
|
formats.add_argument(
|
|
|
|
'--plan', '-p', action='store_const', dest='format', const='plan',
|
|
|
|
help='Shows all migrations in the order they will be applied.',
|
|
|
|
)
|
2014-09-03 16:51:07 +08:00
|
|
|
|
|
|
|
parser.set_defaults(format='list')
|
|
|
|
|
|
|
|
def handle(self, *args, **options):
|
2016-03-03 10:01:36 +08:00
|
|
|
self.verbosity = options['verbosity']
|
2014-09-03 16:51:07 +08:00
|
|
|
|
|
|
|
# Get the database we're operating from
|
2016-03-03 10:01:36 +08:00
|
|
|
db = options['database']
|
2014-09-03 16:51:07 +08:00
|
|
|
connection = connections[db]
|
|
|
|
|
|
|
|
if options['format'] == "plan":
|
|
|
|
return self.show_plan(connection)
|
|
|
|
else:
|
2016-01-12 09:59:34 +08:00
|
|
|
return self.show_list(connection, options['app_label'])
|
2014-09-03 16:51:07 +08:00
|
|
|
|
|
|
|
def show_list(self, connection, app_names=None):
|
|
|
|
"""
|
|
|
|
Shows a list of all migrations on the system, or only those of
|
|
|
|
some named apps.
|
|
|
|
"""
|
|
|
|
# Load migrations from disk/DB
|
2015-01-01 06:26:15 +08:00
|
|
|
loader = MigrationLoader(connection, ignore_no_migrations=True)
|
2014-09-03 16:51:07 +08:00
|
|
|
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)))
|
|
|
|
# Otherwise, show all apps in alphabetic order
|
|
|
|
else:
|
|
|
|
app_names = sorted(loader.migrated_apps)
|
|
|
|
# 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()
|
|
|
|
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:
|
|
|
|
# Give it a nice title if it's a squashed one
|
|
|
|
title = plan_node[1]
|
|
|
|
if graph.nodes[plan_node].replaces:
|
|
|
|
title += " (%s squashed migrations)" % len(graph.nodes[plan_node].replaces)
|
|
|
|
# Mark it as applied/unapplied
|
|
|
|
if plan_node in loader.applied_migrations:
|
|
|
|
self.stdout.write(" [X] %s" % title)
|
|
|
|
else:
|
|
|
|
self.stdout.write(" [ ] %s" % title)
|
|
|
|
shown.add(plan_node)
|
|
|
|
# If we didn't print anything, then a small message
|
|
|
|
if not shown:
|
2015-11-18 07:33:03 +08:00
|
|
|
self.stdout.write(" (no migrations)", self.style.ERROR)
|
2014-09-03 16:51:07 +08:00
|
|
|
|
|
|
|
def show_plan(self, connection):
|
|
|
|
"""
|
|
|
|
Shows all known migrations in the order they will be applied
|
|
|
|
"""
|
|
|
|
# Load migrations from disk/DB
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
graph = loader.graph
|
|
|
|
targets = graph.leaf_nodes()
|
|
|
|
plan = []
|
|
|
|
seen = set()
|
|
|
|
|
|
|
|
# Generate the plan
|
|
|
|
for target in targets:
|
|
|
|
for migration in graph.forwards_plan(target):
|
|
|
|
if migration not in seen:
|
|
|
|
plan.append(graph.nodes[migration])
|
|
|
|
seen.add(migration)
|
|
|
|
|
|
|
|
# Output
|
|
|
|
def print_deps(migration):
|
|
|
|
out = []
|
|
|
|
for dep in migration.dependencies:
|
|
|
|
if dep[1] == "__first__":
|
|
|
|
roots = graph.root_nodes(dep[0])
|
|
|
|
dep = roots[0] if roots else (dep[0], "__first__")
|
|
|
|
out.append("%s.%s" % dep)
|
|
|
|
if out:
|
|
|
|
return " ... (%s)" % ", ".join(out)
|
|
|
|
return ""
|
|
|
|
|
|
|
|
for migration in plan:
|
|
|
|
deps = ""
|
|
|
|
if self.verbosity >= 2:
|
|
|
|
deps = print_deps(migration)
|
|
|
|
if (migration.app_label, migration.name) in loader.applied_migrations:
|
|
|
|
self.stdout.write("[X] %s%s" % (migration, deps))
|
|
|
|
else:
|
|
|
|
self.stdout.write("[ ] %s%s" % (migration, deps))
|