mirror of https://github.com/django/django.git
Fixed #23359 -- Added showmigrations command to list migrations and plan.
Thanks to Collin Anderson, Tim Graham, Gabe Jackson, and Marc Tamlyn for their input, ideas, and review.
This commit is contained in:
parent
8952757698
commit
a1487deebf
|
@ -5,6 +5,7 @@ from collections import OrderedDict
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
import itertools
|
import itertools
|
||||||
import traceback
|
import traceback
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
@ -13,9 +14,10 @@ from django.core.management.color import no_style
|
||||||
from django.core.management.sql import custom_sql_for_model, emit_post_migrate_signal, emit_pre_migrate_signal
|
from django.core.management.sql import custom_sql_for_model, emit_post_migrate_signal, emit_pre_migrate_signal
|
||||||
from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
|
from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
|
||||||
from django.db.migrations.executor import MigrationExecutor
|
from django.db.migrations.executor import MigrationExecutor
|
||||||
from django.db.migrations.loader import MigrationLoader, AmbiguityError
|
from django.db.migrations.loader import AmbiguityError
|
||||||
from django.db.migrations.state import ProjectState
|
from django.db.migrations.state import ProjectState
|
||||||
from django.db.migrations.autodetector import MigrationAutodetector
|
from django.db.migrations.autodetector import MigrationAutodetector
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.utils.module_loading import module_has_submodule
|
from django.utils.module_loading import module_has_submodule
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +64,20 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# If they asked for a migration listing, quit main execution flow and show it
|
# If they asked for a migration listing, quit main execution flow and show it
|
||||||
if options.get("list", False):
|
if options.get("list", False):
|
||||||
return self.show_migration_list(connection, [options['app_label']] if options['app_label'] else None)
|
warnings.warn(
|
||||||
|
"The 'migrate --list' command is deprecated. Use 'showmigrations' instead.",
|
||||||
|
RemovedInDjango20Warning, stacklevel=2)
|
||||||
|
return call_command(
|
||||||
|
'showmigrations',
|
||||||
|
'--list',
|
||||||
|
app_labels=[options['app_label']] if options['app_label'] else None,
|
||||||
|
database=db,
|
||||||
|
no_color=options.get('no-color'),
|
||||||
|
settings=options.get('settings'),
|
||||||
|
stdout=options.get('stdout', self.stdout),
|
||||||
|
traceback=self.show_traceback,
|
||||||
|
verbosity=self.verbosity,
|
||||||
|
)
|
||||||
|
|
||||||
# Hook for backends needing any database preparation
|
# Hook for backends needing any database preparation
|
||||||
connection.prepare_database()
|
connection.prepare_database()
|
||||||
|
@ -325,44 +340,3 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
return created_models
|
return created_models
|
||||||
|
|
||||||
def show_migration_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
|
|
||||||
loader = MigrationLoader(connection)
|
|
||||||
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:
|
|
||||||
self.stdout.write(" (no migrations)", self.style.MIGRATE_FAILURE)
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
|
from django.db.migrations.loader import MigrationLoader
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Shows all available migrations for the current project"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('app_labels', 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.')
|
||||||
|
|
||||||
|
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.')
|
||||||
|
formats.add_argument('--plan', '-p', action='store_const', dest='format', const='plan',
|
||||||
|
help='Shows all migrations in the order they will be applied.')
|
||||||
|
|
||||||
|
parser.set_defaults(format='list')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.verbosity = options.get('verbosity')
|
||||||
|
|
||||||
|
# Get the database we're operating from
|
||||||
|
db = options.get('database')
|
||||||
|
connection = connections[db]
|
||||||
|
|
||||||
|
if options['format'] == "plan":
|
||||||
|
return self.show_plan(connection)
|
||||||
|
else:
|
||||||
|
return self.show_list(connection, options['app_labels'])
|
||||||
|
|
||||||
|
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
|
||||||
|
loader = MigrationLoader(connection)
|
||||||
|
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:
|
||||||
|
self.stdout.write(" (no migrations)", self.style.MIGRATE_FAILURE)
|
||||||
|
|
||||||
|
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))
|
|
@ -123,6 +123,8 @@ details on these changes.
|
||||||
|
|
||||||
* Private attribute ``django.db.models.Field.related`` will be removed.
|
* Private attribute ``django.db.models.Field.related`` will be removed.
|
||||||
|
|
||||||
|
* The ``--list`` option of the ``migrate`` management command will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.9:
|
.. _deprecation-removed-in-1.9:
|
||||||
|
|
||||||
1.9
|
1.9
|
||||||
|
|
|
@ -770,15 +770,10 @@ be warned that using ``--fake`` runs the risk of putting the migration state
|
||||||
table into a state where manual recovery will be needed to make migrations
|
table into a state where manual recovery will be needed to make migrations
|
||||||
run correctly.
|
run correctly.
|
||||||
|
|
||||||
.. django-admin-option:: --list, -l
|
.. deprecated:: 1.8
|
||||||
|
|
||||||
The ``--list`` option will list all of the apps Django knows about, the
|
|
||||||
migrations available for each app and if they are applied or not (marked by
|
|
||||||
an ``[X]`` next to the migration name).
|
|
||||||
|
|
||||||
Apps without migrations are also included in the list, but will have
|
|
||||||
``(no migrations)`` printed under them.
|
|
||||||
|
|
||||||
|
The ``--list`` option has been moved to the :djadmin:`showmigrations`
|
||||||
|
command.
|
||||||
|
|
||||||
runfcgi [options]
|
runfcgi [options]
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -1088,6 +1083,32 @@ behavior you can use the ``--no-startup`` option. e.g.::
|
||||||
|
|
||||||
django-admin shell --plain --no-startup
|
django-admin shell --plain --no-startup
|
||||||
|
|
||||||
|
showmigrations [<app_label> [<app_label>]]
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
.. django-admin:: showmigrations
|
||||||
|
|
||||||
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
Shows all migrations in a project.
|
||||||
|
|
||||||
|
.. django-admin-option:: --list, -l
|
||||||
|
|
||||||
|
The ``--list`` option lists all of the apps Django knows about, the
|
||||||
|
migrations available for each app, and whether or not each migrations is
|
||||||
|
applied (marked by an ``[X]`` next to the migration name).
|
||||||
|
|
||||||
|
Apps without migrations are also listed, but have ``(no migrations)`` printed
|
||||||
|
under them.
|
||||||
|
|
||||||
|
.. django-admin-option:: --plan, -p
|
||||||
|
|
||||||
|
The ``--plan`` option shows the migration plan Django will follow to apply
|
||||||
|
migrations. Any supplied app labels are ignored because the plan might go
|
||||||
|
beyond those apps. Same as ``--list``, applied migrations are marked by an
|
||||||
|
``[X]``. For a verbosity of 2 and above, all dependencies of a migration will
|
||||||
|
also be shown.
|
||||||
|
|
||||||
sql <app_label app_label ...>
|
sql <app_label app_label ...>
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|
|
@ -394,6 +394,9 @@ Management Commands
|
||||||
* :djadmin:`makemigrations` now supports an :djadminopt:`--exit` option to
|
* :djadmin:`makemigrations` now supports an :djadminopt:`--exit` option to
|
||||||
exit with an error code if no migrations are created.
|
exit with an error code if no migrations are created.
|
||||||
|
|
||||||
|
* The new :djadmin:`showmigrations` command allows listing all migrations and
|
||||||
|
their dependencies in a project.
|
||||||
|
|
||||||
Middleware
|
Middleware
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -1134,6 +1137,13 @@ The class :class:`~django.core.management.NoArgsCommand` is now deprecated and
|
||||||
will be removed in Django 2.0. Use :class:`~django.core.management.BaseCommand`
|
will be removed in Django 2.0. Use :class:`~django.core.management.BaseCommand`
|
||||||
instead, which takes no arguments by default.
|
instead, which takes no arguments by default.
|
||||||
|
|
||||||
|
Listing all migrations in a project
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``--list`` option of the :djadmin:`migrate` management command is
|
||||||
|
deprecated and will be removed in Django 2.0. Use :djadmin:`showmigrations`
|
||||||
|
instead.
|
||||||
|
|
||||||
``cache_choices`` option of ``ModelChoiceField`` and ``ModelMultipleChoiceField``
|
``cache_choices`` option of ``ModelChoiceField`` and ``ModelMultipleChoiceField``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ from django.apps import apps
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
from django.core.management import call_command, CommandError
|
from django.core.management import call_command, CommandError
|
||||||
from django.db.migrations import questioner
|
from django.db.migrations import questioner
|
||||||
from django.test import override_settings
|
from django.test import ignore_warnings, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from .models import UnicodeModel, UnserializableModel
|
from .models import UnicodeModel, UnserializableModel
|
||||||
|
@ -50,6 +51,15 @@ class MigrateTests(MigrationTestBase):
|
||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_tribble")
|
||||||
self.assertTableNotExists("migrations_book")
|
self.assertTableNotExists("migrations_book")
|
||||||
|
|
||||||
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
||||||
|
def test_migrate_conflict_exit(self):
|
||||||
|
"""
|
||||||
|
Makes sure that migrate exits if it detects a conflict.
|
||||||
|
"""
|
||||||
|
with self.assertRaisesMessage(CommandError, "Conflicting migrations detected"):
|
||||||
|
call_command("migrate", "migrations")
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango20Warning)
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
||||||
def test_migrate_list(self):
|
def test_migrate_list(self):
|
||||||
"""
|
"""
|
||||||
|
@ -72,13 +82,137 @@ class MigrateTests(MigrationTestBase):
|
||||||
# Cleanup by unmigrating everything
|
# Cleanup by unmigrating everything
|
||||||
call_command("migrate", "migrations", "zero", verbosity=0)
|
call_command("migrate", "migrations", "zero", verbosity=0)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
||||||
def test_migrate_conflict_exit(self):
|
def test_showmigrations_list(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that migrate exits if it detects a conflict.
|
Tests --list output of showmigrations command
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(CommandError):
|
out = six.StringIO()
|
||||||
call_command("migrate", "migrations")
|
call_command("showmigrations", format='list', stdout=out, verbosity=0)
|
||||||
|
self.assertIn("migrations", out.getvalue().lower())
|
||||||
|
self.assertIn("[ ] 0001_initial", out.getvalue().lower())
|
||||||
|
self.assertIn("[ ] 0002_second", out.getvalue().lower())
|
||||||
|
|
||||||
|
call_command("migrate", "migrations", "0001", verbosity=0)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
# Giving the explicit app_label tests for selective `show_list` in the command
|
||||||
|
call_command("showmigrations", "migrations", format='list', stdout=out, verbosity=0)
|
||||||
|
self.assertIn("migrations", out.getvalue().lower())
|
||||||
|
self.assertIn("[x] 0001_initial", out.getvalue().lower())
|
||||||
|
self.assertIn("[ ] 0002_second", out.getvalue().lower())
|
||||||
|
# Cleanup by unmigrating everything
|
||||||
|
call_command("migrate", "migrations", "zero", verbosity=0)
|
||||||
|
|
||||||
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_run_before"})
|
||||||
|
def test_showmigrations_plan(self):
|
||||||
|
"""
|
||||||
|
Tests --plan output of showmigrations command
|
||||||
|
"""
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out)
|
||||||
|
self.assertIn(
|
||||||
|
"[ ] migrations.0001_initial\n"
|
||||||
|
"[ ] migrations.0003_third\n"
|
||||||
|
"[ ] migrations.0002_second",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out, verbosity=2)
|
||||||
|
self.assertIn(
|
||||||
|
"[ ] migrations.0001_initial\n"
|
||||||
|
"[ ] migrations.0003_third ... (migrations.0001_initial)\n"
|
||||||
|
"[ ] migrations.0002_second ... (migrations.0001_initial)",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
call_command("migrate", "migrations", "0003", verbosity=0)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out)
|
||||||
|
self.assertIn(
|
||||||
|
"[x] migrations.0001_initial\n"
|
||||||
|
"[x] migrations.0003_third\n"
|
||||||
|
"[ ] migrations.0002_second",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out, verbosity=2)
|
||||||
|
self.assertIn(
|
||||||
|
"[x] migrations.0001_initial\n"
|
||||||
|
"[x] migrations.0003_third ... (migrations.0001_initial)\n"
|
||||||
|
"[ ] migrations.0002_second ... (migrations.0001_initial)",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cleanup by unmigrating everything
|
||||||
|
call_command("migrate", "migrations", "zero", verbosity=0)
|
||||||
|
|
||||||
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_empty"})
|
||||||
|
def test_showmigrations_plan_no_migrations(self):
|
||||||
|
"""
|
||||||
|
Tests --plan output of showmigrations command without migrations
|
||||||
|
"""
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out)
|
||||||
|
self.assertEqual("", out.getvalue().lower())
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out, verbosity=2)
|
||||||
|
self.assertEqual("", out.getvalue().lower())
|
||||||
|
|
||||||
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_complex"})
|
||||||
|
def test_showmigrations_plan_squashed(self):
|
||||||
|
"""
|
||||||
|
Tests --plan output of showmigrations command with squashed migrations.
|
||||||
|
"""
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out)
|
||||||
|
self.assertEqual(
|
||||||
|
"[ ] migrations.1_auto\n"
|
||||||
|
"[ ] migrations.2_auto\n"
|
||||||
|
"[ ] migrations.3_squashed_5\n"
|
||||||
|
"[ ] migrations.6_auto\n"
|
||||||
|
"[ ] migrations.7_auto\n",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out, verbosity=2)
|
||||||
|
self.assertEqual(
|
||||||
|
"[ ] migrations.1_auto\n"
|
||||||
|
"[ ] migrations.2_auto ... (migrations.1_auto)\n"
|
||||||
|
"[ ] migrations.3_squashed_5 ... (migrations.2_auto)\n"
|
||||||
|
"[ ] migrations.6_auto ... (migrations.3_squashed_5)\n"
|
||||||
|
"[ ] migrations.7_auto ... (migrations.6_auto)\n",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
call_command("migrate", "migrations", "3_squashed_5", verbosity=0)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out)
|
||||||
|
self.assertEqual(
|
||||||
|
"[x] migrations.1_auto\n"
|
||||||
|
"[x] migrations.2_auto\n"
|
||||||
|
"[x] migrations.3_squashed_5\n"
|
||||||
|
"[ ] migrations.6_auto\n"
|
||||||
|
"[ ] migrations.7_auto\n",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
out = six.StringIO()
|
||||||
|
call_command("showmigrations", format='plan', stdout=out, verbosity=2)
|
||||||
|
self.assertEqual(
|
||||||
|
"[x] migrations.1_auto\n"
|
||||||
|
"[x] migrations.2_auto ... (migrations.1_auto)\n"
|
||||||
|
"[x] migrations.3_squashed_5 ... (migrations.2_auto)\n"
|
||||||
|
"[ ] migrations.6_auto ... (migrations.3_squashed_5)\n"
|
||||||
|
"[ ] migrations.7_auto ... (migrations.6_auto)\n",
|
||||||
|
out.getvalue().lower()
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
||||||
def test_sqlmigrate(self):
|
def test_sqlmigrate(self):
|
||||||
|
|
Loading…
Reference in New Issue