You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

showmigrations.py 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import sys
  2. from django.apps import apps
  3. from django.core.management.base import BaseCommand
  4. from django.db import DEFAULT_DB_ALIAS, connections
  5. from django.db.migrations.loader import MigrationLoader
  6. class Command(BaseCommand):
  7. help = "Shows all available migrations for the current project"
  8. def add_arguments(self, parser):
  9. parser.add_argument(
  10. 'app_label', nargs='*',
  11. help='App labels of applications to limit the output to.',
  12. )
  13. parser.add_argument(
  14. '--database', default=DEFAULT_DB_ALIAS,
  15. help='Nominates a database to synchronize. Defaults to the "default" database.',
  16. )
  17. formats = parser.add_mutually_exclusive_group()
  18. formats.add_argument(
  19. '--list', '-l', action='store_const', dest='format', const='list',
  20. help='Shows a list of all migrations and which are applied.',
  21. )
  22. formats.add_argument(
  23. '--plan', '-p', action='store_const', dest='format', const='plan',
  24. help=(
  25. 'Shows all migrations in the order they will be applied. '
  26. 'With a verbosity level of 2 or above all direct migration dependencies '
  27. 'and reverse dependencies (run_before) will be included.'
  28. )
  29. )
  30. parser.set_defaults(format='list')
  31. def handle(self, *args, **options):
  32. self.verbosity = options['verbosity']
  33. # Get the database we're operating from
  34. db = options['database']
  35. connection = connections[db]
  36. if options['format'] == "plan":
  37. return self.show_plan(connection, options['app_label'])
  38. else:
  39. return self.show_list(connection, options['app_label'])
  40. def _validate_app_names(self, loader, app_names):
  41. has_bad_names = False
  42. for app_name in app_names:
  43. try:
  44. apps.get_app_config(app_name)
  45. except LookupError as err:
  46. self.stderr.write(str(err))
  47. has_bad_names = True
  48. if has_bad_names:
  49. sys.exit(2)
  50. def show_list(self, connection, app_names=None):
  51. """
  52. Show a list of all migrations on the system, or only those of
  53. some named apps.
  54. """
  55. # Load migrations from disk/DB
  56. loader = MigrationLoader(connection, ignore_no_migrations=True)
  57. graph = loader.graph
  58. # If we were passed a list of apps, validate it
  59. if app_names:
  60. self._validate_app_names(loader, app_names)
  61. # Otherwise, show all apps in alphabetic order
  62. else:
  63. app_names = sorted(loader.migrated_apps)
  64. # For each app, print its migrations in order from oldest (roots) to
  65. # newest (leaves).
  66. for app_name in app_names:
  67. self.stdout.write(app_name, self.style.MIGRATE_LABEL)
  68. shown = set()
  69. for node in graph.leaf_nodes(app_name):
  70. for plan_node in graph.forwards_plan(node):
  71. if plan_node not in shown and plan_node[0] == app_name:
  72. # Give it a nice title if it's a squashed one
  73. title = plan_node[1]
  74. if graph.nodes[plan_node].replaces:
  75. title += " (%s squashed migrations)" % len(graph.nodes[plan_node].replaces)
  76. # Mark it as applied/unapplied
  77. if plan_node in loader.applied_migrations:
  78. self.stdout.write(" [X] %s" % title)
  79. else:
  80. self.stdout.write(" [ ] %s" % title)
  81. shown.add(plan_node)
  82. # If we didn't print anything, then a small message
  83. if not shown:
  84. self.stdout.write(" (no migrations)", self.style.ERROR)
  85. def show_plan(self, connection, app_names=None):
  86. """
  87. Show all known migrations (or only those of the specified app_names)
  88. in the order they will be applied.
  89. """
  90. # Load migrations from disk/DB
  91. loader = MigrationLoader(connection)
  92. graph = loader.graph
  93. if app_names:
  94. self._validate_app_names(loader, app_names)
  95. targets = [key for key in graph.leaf_nodes() if key[0] in app_names]
  96. else:
  97. targets = graph.leaf_nodes()
  98. plan = []
  99. seen = set()
  100. # Generate the plan
  101. for target in targets:
  102. for migration in graph.forwards_plan(target):
  103. if migration not in seen:
  104. node = graph.node_map[migration]
  105. plan.append(node)
  106. seen.add(migration)
  107. # Output
  108. def print_deps(node):
  109. out = []
  110. for parent in sorted(node.parents):
  111. out.append("%s.%s" % parent.key)
  112. if out:
  113. return " ... (%s)" % ", ".join(out)
  114. return ""
  115. for node in plan:
  116. deps = ""
  117. if self.verbosity >= 2:
  118. deps = print_deps(node)
  119. if node.key in loader.applied_migrations:
  120. self.stdout.write("[X] %s.%s%s" % (node.key[0], node.key[1], deps))
  121. else:
  122. self.stdout.write("[ ] %s.%s%s" % (node.key[0], node.key[1], deps))
  123. if not plan:
  124. self.stdout.write('(no migrations)', self.style.ERROR)