示例#1
0
    def test_soft_apply(self):
        """
        Tests detection of initial migrations already having been applied.
        """
        state = {"faked": None}

        def fake_storer(phase, migration=None, fake=None):
            state["faked"] = fake

        executor = MigrationExecutor(connection, progress_callback=fake_storer)
        # Were the tables there before?
        self.assertTableNotExists("migrations_author")
        self.assertTableNotExists("migrations_tribble")
        # Run it normally
        self.assertEqual(
            executor.migration_plan([("migrations", "0001_initial")]),
            [
                (executor.loader.graph.nodes["migrations",
                                             "0001_initial"], False),
            ],
        )
        executor.migrate([("migrations", "0001_initial")])
        # Are the tables there now?
        self.assertTableExists("migrations_author")
        self.assertTableExists("migrations_tribble")
        # We shouldn't have faked that one
        self.assertIs(state["faked"], False)
        # Rebuild the graph to reflect the new DB state
        executor.loader.build_graph()
        # Fake-reverse that
        executor.migrate([("migrations", None)], fake=True)
        # Are the tables still there?
        self.assertTableExists("migrations_author")
        self.assertTableExists("migrations_tribble")
        # Make sure that was faked
        self.assertIs(state["faked"], True)
        # Finally, migrate forwards; this should fake-apply our initial migration
        executor.loader.build_graph()
        self.assertEqual(
            executor.migration_plan([("migrations", "0001_initial")]),
            [
                (executor.loader.graph.nodes["migrations",
                                             "0001_initial"], False),
            ],
        )
        # Applying the migration should raise a database level error
        # because we haven't given the --fake-initial option
        with self.assertRaises(DatabaseError):
            executor.migrate([("migrations", "0001_initial")])
        # Reset the faked state
        state = {"faked": None}
        # Allow faking of initial CreateModel operations
        executor.migrate([("migrations", "0001_initial")], fake_initial=True)
        self.assertIs(state["faked"], True)
        # And migrate back to clean up the database
        executor.loader.build_graph()
        executor.migrate([("migrations", None)])
        self.assertTableNotExists("migrations_author")
        self.assertTableNotExists("migrations_tribble")
示例#2
0
    def test_detect_soft_applied_add_field_manytomanyfield(self):
        """
        executor.detect_soft_applied() detects ManyToManyField tables from an
        AddField operation. This checks the case of AddField in a migration
        with other operations (0001) and the case of AddField in its own
        migration (0002).
        """
        tables = [
            # from 0001
            "migrations_project",
            "migrations_task",
            "migrations_project_tasks",
            # from 0002
            "migrations_task_projects",
        ]
        executor = MigrationExecutor(connection)
        # Create the tables for 0001 but make it look like the migration hasn't
        # been applied.
        executor.migrate([("migrations", "0001_initial")])
        executor.migrate([("migrations", None)], fake=True)
        for table in tables[:3]:
            self.assertTableExists(table)
        # Table detection sees 0001 is applied but not 0002.
        migration = executor.loader.get_migration("migrations", "0001_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], True)
        migration = executor.loader.get_migration("migrations", "0002_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], False)

        # Create the tables for both migrations but make it look like neither
        # has been applied.
        executor.loader.build_graph()
        executor.migrate([("migrations", "0001_initial")], fake=True)
        executor.migrate([("migrations", "0002_initial")])
        executor.loader.build_graph()
        executor.migrate([("migrations", None)], fake=True)
        # Table detection sees 0002 is applied.
        migration = executor.loader.get_migration("migrations", "0002_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], True)

        # Leave the tables for 0001 except the many-to-many table. That missing
        # table should cause detect_soft_applied() to return False.
        with connection.schema_editor() as editor:
            for table in tables[2:]:
                editor.execute(editor.sql_delete_table % {"table": table})
        migration = executor.loader.get_migration("migrations", "0001_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], False)

        # Cleanup by removing the remaining tables.
        with connection.schema_editor() as editor:
            for table in tables[:2]:
                editor.execute(editor.sql_delete_table % {"table": table})
        for table in tables:
            self.assertTableNotExists(table)
示例#3
0
 def test_non_atomic_migration(self):
     """
     Applying a non-atomic migration works as expected.
     """
     executor = MigrationExecutor(connection)
     with self.assertRaisesMessage(RuntimeError, "Abort migration"):
         executor.migrate([("migrations", "0001_initial")])
     self.assertTableExists("migrations_publisher")
     migrations_apps = executor.loader.project_state(
         ("migrations", "0001_initial")).apps
     Publisher = migrations_apps.get_model("migrations", "Publisher")
     self.assertTrue(Publisher.objects.exists())
     self.assertTableNotExists("migrations_book")
示例#4
0
    def test_empty_plan(self):
        """
        Re-planning a full migration of a fully-migrated set doesn't
        perform spurious unmigrations and remigrations.

        There was previously a bug where the executor just always performed the
        backwards plan for applied migrations - which even for the most recent
        migration in an app, might include other, dependent apps, and these
        were being unmigrated.
        """
        # Make the initial plan, check it
        executor = MigrationExecutor(connection)
        plan = executor.migration_plan([
            ("migrations", "0002_second"),
            ("migrations2", "0001_initial"),
        ])
        self.assertEqual(
            plan,
            [
                (executor.loader.graph.nodes["migrations",
                                             "0001_initial"], False),
                (executor.loader.graph.nodes["migrations",
                                             "0002_second"], False),
                (executor.loader.graph.nodes["migrations2",
                                             "0001_initial"], False),
            ],
        )
        # Fake-apply all migrations
        executor.migrate([("migrations", "0002_second"),
                          ("migrations2", "0001_initial")],
                         fake=True)
        # Rebuild the graph to reflect the new DB state
        executor.loader.build_graph()
        # Now plan a second time and make sure it's empty
        plan = executor.migration_plan([
            ("migrations", "0002_second"),
            ("migrations2", "0001_initial"),
        ])
        self.assertEqual(plan, [])
        # The resulting state should include applied migrations.
        state = executor.migrate([
            ("migrations", "0002_second"),
            ("migrations2", "0001_initial"),
        ])
        self.assertIn(('migrations', 'book'), state.models)
        self.assertIn(('migrations', 'author'), state.models)
        self.assertIn(('migrations2', 'otherauthor'), state.models)
        # Erase all the fake records
        executor.recorder.record_unapplied("migrations2", "0001_initial")
        executor.recorder.record_unapplied("migrations", "0002_second")
        executor.recorder.record_unapplied("migrations", "0001_initial")
示例#5
0
    def test_process_callback(self):
        """
        #24129 - Tests callback process
        """
        call_args_list = []

        def callback(*args):
            call_args_list.append(args)

        executor = MigrationExecutor(connection, progress_callback=callback)
        # Were the tables there before?
        self.assertTableNotExists("migrations_author")
        self.assertTableNotExists("migrations_tribble")
        executor.migrate([
            ("migrations", "0001_initial"),
            ("migrations", "0002_second"),
        ])
        # Rebuild the graph to reflect the new DB state
        executor.loader.build_graph()

        executor.migrate([
            ("migrations", None),
            ("migrations", None),
        ])
        self.assertTableNotExists("migrations_author")
        self.assertTableNotExists("migrations_tribble")

        migrations = executor.loader.graph.nodes
        expected = [
            ("render_start", ),
            ("render_success", ),
            ("apply_start", migrations['migrations', '0001_initial'], False),
            ("apply_success", migrations['migrations', '0001_initial'], False),
            ("apply_start", migrations['migrations', '0002_second'], False),
            ("apply_success", migrations['migrations', '0002_second'], False),
            ("render_start", ),
            ("render_success", ),
            ("unapply_start", migrations['migrations', '0002_second'], False),
            ("unapply_success", migrations['migrations',
                                           '0002_second'], False),
            ("unapply_start", migrations['migrations', '0001_initial'], False),
            ("unapply_success", migrations['migrations',
                                           '0001_initial'], False),
        ]
        self.assertEqual(call_args_list, expected)
示例#6
0
 def test_run_with_squashed(self):
     """
     Tests running a squashed migration from zero (should ignore what it replaces)
     """
     executor = MigrationExecutor(connection)
     # Check our leaf node is the squashed one
     leaves = [
         key for key in executor.loader.graph.leaf_nodes()
         if key[0] == "migrations"
     ]
     self.assertEqual(leaves, [("migrations", "0001_squashed_0002")])
     # Check the plan
     plan = executor.migration_plan([("migrations", "0001_squashed_0002")])
     self.assertEqual(
         plan,
         [
             (executor.loader.graph.nodes["migrations",
                                          "0001_squashed_0002"], False),
         ],
     )
     # Were the tables there before?
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_book")
     # Alright, let's try running it
     executor.migrate([("migrations", "0001_squashed_0002")])
     # Are the tables there now?
     self.assertTableExists("migrations_author")
     self.assertTableExists("migrations_book")
     # Rebuild the graph to reflect the new DB state
     executor.loader.build_graph()
     # Alright, let's undo what we did. Should also just use squashed.
     plan = executor.migration_plan([("migrations", None)])
     self.assertEqual(
         plan,
         [
             (executor.loader.graph.nodes["migrations",
                                          "0001_squashed_0002"], True),
         ],
     )
     executor.migrate([("migrations", None)])
     # Are the tables gone?
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_book")
示例#7
0
    def test_migrate_marks_replacement_applied_even_if_it_did_nothing(self):
        """
        A new squash migration will be marked as applied even if all its
        replaced migrations were previously already applied (#24628).
        """
        recorder = MigrationRecorder(connection)
        # Record all replaced migrations as applied
        recorder.record_applied("migrations", "0001_initial")
        recorder.record_applied("migrations", "0002_second")
        executor = MigrationExecutor(connection)
        executor.migrate([("migrations", "0001_squashed_0002")])

        # Because 0001 and 0002 are both applied, even though this migrate run
        # didn't apply anything new, their squashed replacement should be
        # marked as applied.
        self.assertIn(
            ("migrations", "0001_squashed_0002"),
            recorder.applied_migrations(),
        )
示例#8
0
 def test_run(self):
     """
     Tests running a simple set of migrations.
     """
     executor = MigrationExecutor(connection)
     # Let's look at the plan first and make sure it's up to scratch
     plan = executor.migration_plan([("migrations", "0002_second")])
     self.assertEqual(
         plan,
         [
             (executor.loader.graph.nodes["migrations",
                                          "0001_initial"], False),
             (executor.loader.graph.nodes["migrations",
                                          "0002_second"], False),
         ],
     )
     # Were the tables there before?
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_book")
     # Alright, let's try running it
     executor.migrate([("migrations", "0002_second")])
     # Are the tables there now?
     self.assertTableExists("migrations_author")
     self.assertTableExists("migrations_book")
     # Rebuild the graph to reflect the new DB state
     executor.loader.build_graph()
     # Alright, let's undo what we did
     plan = executor.migration_plan([("migrations", None)])
     self.assertEqual(
         plan,
         [
             (executor.loader.graph.nodes["migrations",
                                          "0002_second"], True),
             (executor.loader.graph.nodes["migrations",
                                          "0001_initial"], True),
         ],
     )
     executor.migrate([("migrations", None)])
     # Are the tables gone?
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_book")
示例#9
0
    def test_apply_all_replaced_marks_replacement_as_applied(self):
        """
        Applying all replaced migrations marks replacement as applied (#24628).
        """
        recorder = MigrationRecorder(connection)
        # Place the database in a state where the replaced migrations are
        # partially applied: 0001 is applied, 0002 is not.
        recorder.record_applied("migrations", "0001_initial")
        executor = MigrationExecutor(connection)
        # Use fake because we don't actually have the first migration
        # applied, so the second will fail. And there's no need to actually
        # create/modify tables here, we're just testing the
        # MigrationRecord, which works the same with or without fake.
        executor.migrate([("migrations", "0002_second")], fake=True)

        # Because we've now applied 0001 and 0002 both, their squashed
        # replacement should be marked as applied.
        self.assertIn(
            ("migrations", "0001_squashed_0002"),
            recorder.applied_migrations(),
        )
示例#10
0
    def test_alter_id_type_with_fk(self):
        try:
            executor = MigrationExecutor(connection)
            self.assertTableNotExists("author_app_author")
            self.assertTableNotExists("book_app_book")
            # Apply initial migrations
            executor.migrate([
                ("author_app", "0001_initial"),
                ("book_app", "0001_initial"),
            ])
            self.assertTableExists("author_app_author")
            self.assertTableExists("book_app_book")
            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()

            # Apply PK type alteration
            executor.migrate([("author_app", "0002_alter_id")])

            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()
        finally:
            # We can't simply unapply the migrations here because there is no
            # implicit cast from VARCHAR to INT on the database level.
            with connection.schema_editor() as editor:
                editor.execute(editor.sql_delete_table %
                               {"table": "book_app_book"})
                editor.execute(editor.sql_delete_table %
                               {"table": "author_app_author"})
            self.assertTableNotExists("author_app_author")
            self.assertTableNotExists("book_app_book")
            executor.migrate([("author_app", None)], fake=True)
示例#11
0
 def test_unrelated_applied_migrations_mutate_state(self):
     """
     #26647 - Unrelated applied migrations should be part of the final
     state in both directions.
     """
     executor = MigrationExecutor(connection)
     executor.migrate([
         ('mutate_state_b', '0002_add_field'),
     ])
     # Migrate forward.
     executor.loader.build_graph()
     state = executor.migrate([
         ('mutate_state_a', '0001_initial'),
     ])
     self.assertIn('added', dict(state.models['mutate_state_b',
                                              'b'].fields))
     executor.loader.build_graph()
     # Migrate backward.
     state = executor.migrate([
         ('mutate_state_a', None),
     ])
     self.assertIn('added', dict(state.models['mutate_state_b',
                                              'b'].fields))
     executor.migrate([
         ('mutate_state_b', None),
     ])
示例#12
0
    def test_unrelated_model_lookups_backwards(self):
        """
        #24123 - All models of apps being unapplied which are
        unrelated to the first app being unapplied are part of the initial
        model state.
        """
        try:
            executor = MigrationExecutor(connection)
            self.assertTableNotExists("lookuperror_a_a1")
            self.assertTableNotExists("lookuperror_b_b1")
            self.assertTableNotExists("lookuperror_c_c1")
            executor.migrate([
                ("lookuperror_a", "0004_a4"),
                ("lookuperror_b", "0003_b3"),
                ("lookuperror_c", "0003_c3"),
            ])
            self.assertTableExists("lookuperror_b_b3")
            self.assertTableExists("lookuperror_a_a4")
            self.assertTableExists("lookuperror_c_c3")
            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()

            # Migrate backwards -- This led to a lookup LookupErrors because
            # lookuperror_b.B2 is not in the initial state (unrelated to app c)
            executor.migrate([("lookuperror_a", None)])

            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()
        finally:
            # Cleanup
            executor.migrate([("lookuperror_b", None),
                              ("lookuperror_c", None)])
            self.assertTableNotExists("lookuperror_a_a1")
            self.assertTableNotExists("lookuperror_b_b1")
            self.assertTableNotExists("lookuperror_c_c1")
示例#13
0
    def test_mixed_plan_not_supported(self):
        """
        Although the MigrationExecutor interfaces allows for mixed migration
        plans (combined forwards and backwards migrations) this is not
        supported.
        """
        # Prepare for mixed plan
        executor = MigrationExecutor(connection)
        plan = executor.migration_plan([("migrations", "0002_second")])
        self.assertEqual(
            plan,
            [
                (executor.loader.graph.nodes["migrations",
                                             "0001_initial"], False),
                (executor.loader.graph.nodes["migrations",
                                             "0002_second"], False),
            ],
        )
        executor.migrate(None, plan)
        # Rebuild the graph to reflect the new DB state
        executor.loader.build_graph()
        self.assertIn(('migrations', '0001_initial'),
                      executor.loader.applied_migrations)
        self.assertIn(('migrations', '0002_second'),
                      executor.loader.applied_migrations)
        self.assertNotIn(('migrations2', '0001_initial'),
                         executor.loader.applied_migrations)

        # Generate mixed plan
        plan = executor.migration_plan([
            ("migrations", None),
            ("migrations2", "0001_initial"),
        ])
        msg = (
            'Migration plans with both forwards and backwards migrations are '
            'not supported. Please split your migration process into separate '
            'plans of only forwards OR backwards migrations.')
        with self.assertRaisesMessage(InvalidMigrationPlan, msg) as cm:
            executor.migrate(None, plan)
        self.assertEqual(
            cm.exception.args[1],
            [
                (executor.loader.graph.nodes["migrations",
                                             "0002_second"], True),
                (executor.loader.graph.nodes["migrations",
                                             "0001_initial"], True),
                (executor.loader.graph.nodes["migrations2",
                                             "0001_initial"], False),
            ],
        )
        # Rebuild the graph to reflect the new DB state
        executor.loader.build_graph()
        executor.migrate([
            ("migrations", None),
            ("migrations2", None),
        ])
        # Are the tables gone?
        self.assertTableNotExists("migrations_author")
        self.assertTableNotExists("migrations_book")
        self.assertTableNotExists("migrations2_otherauthor")
示例#14
0
 def test_custom_user(self):
     """
     Regression test for #22325 - references to a custom user model defined in the
     same app are not resolved correctly.
     """
     executor = MigrationExecutor(connection)
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_tribble")
     # Migrate forwards
     executor.migrate([("migrations", "0001_initial")])
     self.assertTableExists("migrations_author")
     self.assertTableExists("migrations_tribble")
     # Make sure the soft-application detection works (#23093)
     # Change table_names to not return auth_user during this as
     # it wouldn't be there in a normal run, and ensure migrations.Author
     # exists in the global app registry temporarily.
     old_table_names = connection.introspection.table_names
     connection.introspection.table_names = lambda c: [
         x for x in old_table_names(c) if x != "auth_user"
     ]
     migrations_apps = executor.loader.project_state(
         ("migrations", "0001_initial")).apps
     global_apps.get_app_config(
         "migrations").models["author"] = migrations_apps.get_model(
             "migrations", "author")
     try:
         migration = executor.loader.get_migration("auth", "0001_initial")
         self.assertIs(
             executor.detect_soft_applied(None, migration)[0], True)
     finally:
         connection.introspection.table_names = old_table_names
         del global_apps.get_app_config("migrations").models["author"]
     # And migrate back to clean up the database
     executor.loader.build_graph()
     executor.migrate([("migrations", None)])
     self.assertTableNotExists("migrations_author")
     self.assertTableNotExists("migrations_tribble")
示例#15
0
 def test_atomic_operation_in_non_atomic_migration(self):
     """
     An atomic operation is properly rolled back inside a non-atomic
     migration.
     """
     executor = MigrationExecutor(connection)
     with self.assertRaisesMessage(RuntimeError, "Abort migration"):
         executor.migrate([("migrations", "0001_initial")])
     migrations_apps = executor.loader.project_state(
         ("migrations", "0001_initial")).apps
     Editor = migrations_apps.get_model("migrations", "Editor")
     self.assertFalse(Editor.objects.exists())
     # Record previous migration as successful.
     executor.migrate([("migrations", "0001_initial")], fake=True)
     # Rebuild the graph to reflect the new DB state.
     executor.loader.build_graph()
     # Migrating backwards is also atomic.
     with self.assertRaisesMessage(RuntimeError, "Abort migration"):
         executor.migrate([("migrations", None)])
     self.assertFalse(Editor.objects.exists())
示例#16
0
    def handle(self, *args, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']

        # Import the 'management' module within each installed app, to register
        # dispatcher events.
        for app_config in apps.get_app_configs():
            if module_has_submodule(app_config.module, "management"):
                import_module('.management', app_config.name)

        # Get the database we're operating from
        db = options['database']
        connection = connections[db]

        # Hook for backends needing any database preparation
        connection.prepare_database()
        # Work out which apps have migrations and which do not
        executor = MigrationExecutor(connection, self.migration_progress_callback)

        # Raise an error if any migrations are applied before their dependencies.
        executor.loader.check_consistent_history(connection)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any
        conflicts = executor.loader.detect_conflicts()
        if conflicts:
            name_str = "; ".join(
                "%s in %s" % (", ".join(names), app)
                for app, names in conflicts.items()
            )
            raise CommandError(
                "Conflicting migrations detected; multiple leaf nodes in the "
                "migration graph: (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str
            )

        # If they supplied command line arguments, work out what they mean.
        target_app_labels_only = True
        if options['app_label']:
            # Validate app_label.
            app_label = options['app_label']
            try:
                apps.get_app_config(app_label)
            except LookupError as err:
                raise CommandError(str(err))
            if app_label not in executor.loader.migrated_apps:
                raise CommandError("App '%s' does not have migrations." % app_label)

        if options['app_label'] and options['migration_name']:
            migration_name = options['migration_name']
            if migration_name == "zero":
                targets = [(app_label, None)]
            else:
                try:
                    migration = executor.loader.get_migration_by_prefix(app_label, migration_name)
                except AmbiguityError:
                    raise CommandError(
                        "More than one migration matches '%s' in app '%s'. "
                        "Please be more specific." %
                        (migration_name, app_label)
                    )
                except KeyError:
                    raise CommandError("Cannot find a migration matching '%s' from app '%s'." % (
                        migration_name, app_label))
                targets = [(app_label, migration.name)]
            target_app_labels_only = False
        elif options['app_label']:
            targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label]
        else:
            targets = executor.loader.graph.leaf_nodes()

        plan = executor.migration_plan(targets)

        if options['plan']:
            self.stdout.write('Planned operations:', self.style.MIGRATE_LABEL)
            if not plan:
                self.stdout.write('  No planned migration operations.')
            for migration, backwards in plan:
                self.stdout.write(str(migration), self.style.MIGRATE_HEADING)
                for operation in migration.operations:
                    message, is_error = self.describe_operation(operation, backwards)
                    style = self.style.WARNING if is_error else None
                    self.stdout.write('    ' + message, style)
            return

        run_syncdb = options['run_syncdb'] and executor.loader.unmigrated_apps
        # Print some useful info
        if self.verbosity >= 1:
            self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:"))
            if run_syncdb:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Synchronize unmigrated apps: ") +
                    (", ".join(sorted(executor.loader.unmigrated_apps)))
                )
            if target_app_labels_only:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Apply all migrations: ") +
                    (", ".join(sorted({a for a, n in targets})) or "(none)")
                )
            else:
                if targets[0][1] is None:
                    self.stdout.write(self.style.MIGRATE_LABEL(
                        "  Unapply all migrations: ") + "%s" % (targets[0][0],)
                    )
                else:
                    self.stdout.write(self.style.MIGRATE_LABEL(
                        "  Target specific migration: ") + "%s, from %s"
                        % (targets[0][1], targets[0][0])
                    )

        pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
        pre_migrate_apps = pre_migrate_state.apps
        emit_pre_migrate_signal(
            self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan,
        )

        # Run the syncdb phase.
        if run_syncdb:
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:"))
            self.sync_apps(connection, executor.loader.unmigrated_apps)

        # Migrate!
        if self.verbosity >= 1:
            self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:"))
        if not plan:
            if self.verbosity >= 1:
                self.stdout.write("  No migrations to apply.")
                # If there's changes that aren't in migrations yet, tell them how to fix it.
                autodetector = MigrationAutodetector(
                    executor.loader.project_state(),
                    ProjectState.from_apps(apps),
                )
                changes = autodetector.changes(graph=executor.loader.graph)
                if changes:
                    self.stdout.write(self.style.NOTICE(
                        "  Your models have changes that are not yet reflected "
                        "in a migration, and so won't be applied."
                    ))
                    self.stdout.write(self.style.NOTICE(
                        "  Run 'manage.py makemigrations' to make new "
                        "migrations, and then re-run 'manage.py migrate' to "
                        "apply them."
                    ))
            fake = False
            fake_initial = False
        else:
            fake = options['fake']
            fake_initial = options['fake_initial']
        post_migrate_state = executor.migrate(
            targets, plan=plan, state=pre_migrate_state.clone(), fake=fake,
            fake_initial=fake_initial,
        )
        # post_migrate signals have access to all models. Ensure that all models
        # are reloaded in case any are delayed.
        post_migrate_state.clear_delayed_apps_cache()
        post_migrate_apps = post_migrate_state.apps

        # Re-render models of real apps to include relationships now that
        # we've got a final state. This wouldn't be necessary if real apps
        # models were rendered with relationships in the first place.
        with post_migrate_apps.bulk_update():
            model_keys = []
            for model_state in post_migrate_apps.real_models:
                model_key = model_state.app_label, model_state.name_lower
                model_keys.append(model_key)
                post_migrate_apps.unregister_model(*model_key)
        post_migrate_apps.render_multiple([
            ModelState.from_model(apps.get_model(*model)) for model in model_keys
        ])

        # Send the post_migrate signal, so individual apps can do whatever they need
        # to do at this point.
        emit_post_migrate_signal(
            self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan,
        )