예제 #1
0
    def test_load(self):
        """
        Makes sure the loader can load the migrations for the test apps,
        and then render them out to a new Apps.
        """
        # Load and test the plan
        migration_loader = MigrationLoader(connection)
        self.assertEqual(
            migration_loader.graph.forwards_plan(
                ("migrations", "0002_second")),
            [
                ("migrations", "0001_initial"),
                ("migrations", "0002_second"),
            ],
        )
        # Now render it out!
        project_state = migration_loader.project_state(
            ("migrations", "0002_second"))
        self.assertEqual(len(project_state.models), 2)

        author_state = project_state.models["migrations", "author"]
        self.assertEqual([x for x, y in author_state.fields],
                         ["id", "name", "slug", "age", "rating"])

        book_state = project_state.models["migrations", "book"]
        self.assertEqual([x for x, y in book_state.fields], ["id", "author"])

        # Ensure we've included unmigrated apps in there too
        self.assertIn("basic", project_state.real_apps)
예제 #2
0
    def test_loading_squashed_complex_multi_apps(self):
        loader = MigrationLoader(connection)
        loader.build_graph()

        plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
        expected_plan = {
            ('app1', '1_auto'),
            ('app2', '1_squashed_2'),
            ('app1', '2_squashed_3'),
            ('app1', '4_auto'),
        }
        self.assertEqual(plan, expected_plan)
예제 #3
0
 def test_explicit_missing_module(self):
     """
     If a MIGRATION_MODULES override points to a missing module, the error
     raised during the importation attempt should be propagated unless
     `ignore_no_migrations=True`.
     """
     with self.assertRaisesMessage(ImportError, 'missing-module'):
         migration_loader = MigrationLoader(connection)
     migration_loader = MigrationLoader(connection,
                                        ignore_no_migrations=True)
     self.assertEqual(migration_loader.migrated_apps, set())
     self.assertEqual(migration_loader.unmigrated_apps, {'migrated_app'})
예제 #4
0
    def test_loading_squashed_ref_squashed(self):
        "Tests loading a squashed migration with a new migration referencing it"
        r"""
        The sample migrations are structured like this:

        app_1       1 --> 2 ---------------------*--> 3        *--> 4
                     \                          /             /
                      *-------------------*----/--> 2_sq_3 --*
                       \                 /    /
        =============== \ ============= / == / ======================
        app_2            *--> 1_sq_2 --*    /
                          \                /
                           *--> 1 --> 2 --*

        Where 2_sq_3 is a replacing migration for 2 and 3 in app_1,
        as 1_sq_2 is a replacing migration for 1 and 2 in app_2.
        """

        loader = MigrationLoader(connection)
        recorder = MigrationRecorder(connection)
        self.addCleanup(recorder.flush)

        # Load with nothing applied: both migrations squashed.
        loader.build_graph()
        plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
        plan = plan - loader.applied_migrations
        expected_plan = {
            ('app1', '1_auto'),
            ('app2', '1_squashed_2'),
            ('app1', '2_squashed_3'),
            ('app1', '4_auto'),
        }
        self.assertEqual(plan, expected_plan)

        # Fake-apply a few from app1: unsquashes migration in app1.
        recorder.record_applied('app1', '1_auto')
        recorder.record_applied('app1', '2_auto')
        loader.build_graph()
        plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
        plan = plan - loader.applied_migrations
        expected_plan = {
            ('app2', '1_squashed_2'),
            ('app1', '3_auto'),
            ('app1', '4_auto'),
        }
        self.assertEqual(plan, expected_plan)

        # Fake-apply one from app2: unsquashes migration in app2 too.
        recorder.record_applied('app2', '1_auto')
        loader.build_graph()
        plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
        plan = plan - loader.applied_migrations
        expected_plan = {
            ('app2', '2_auto'),
            ('app1', '3_auto'),
            ('app1', '4_auto'),
        }
        self.assertEqual(plan, expected_plan)
예제 #5
0
 def test_marked_as_unmigrated(self):
     """
     MIGRATION_MODULES allows disabling of migrations for a particular app.
     """
     migration_loader = MigrationLoader(connection)
     self.assertEqual(migration_loader.migrated_apps, set())
     self.assertEqual(migration_loader.unmigrated_apps, {'migrated_app'})
예제 #6
0
 def test_marked_as_migrated(self):
     """
     Undefined MIGRATION_MODULES implies default migration module.
     """
     migration_loader = MigrationLoader(connection)
     self.assertEqual(migration_loader.migrated_apps, {'migrated_app'})
     self.assertEqual(migration_loader.unmigrated_apps, set())
예제 #7
0
    def test_loading_squashed_complex_multi_apps_partially_applied(self):
        loader = MigrationLoader(connection)
        recorder = MigrationRecorder(connection)
        recorder.record_applied('app1', '1_auto')
        recorder.record_applied('app1', '2_auto')
        loader.build_graph()

        plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
        plan = plan - loader.applied_migrations
        expected_plan = {
            ('app2', '1_squashed_2'),
            ('app1', '3_auto'),
            ('app1', '4_auto'),
        }

        self.assertEqual(plan, expected_plan)
예제 #8
0
파일: writer.py 프로젝트: iMerica/dj-models
    def basedir(self):
        migrations_package_name, _ = MigrationLoader.migrations_module(
            self.migration.app_label)

        if migrations_package_name is None:
            raise ValueError(
                "Django can't create migrations for app '%s' because "
                "migrations have been disabled via the MIGRATION_MODULES "
                "setting." % self.migration.app_label)

        # See if we can import the migrations module directly
        try:
            migrations_module = import_module(migrations_package_name)
        except ImportError:
            pass
        else:
            try:
                return module_dir(migrations_module)
            except ValueError:
                pass

        # Alright, see if it's a direct submodule of the app
        app_config = apps.get_app_config(self.migration.app_label)
        maybe_app_name, _, migrations_package_basename = migrations_package_name.rpartition(
            ".")
        if app_config.name == maybe_app_name:
            return os.path.join(app_config.path, migrations_package_basename)

        # In case of using MIGRATION_MODULES setting and the custom package
        # doesn't exist, create one, starting from an existing package
        existing_dirs, missing_dirs = migrations_package_name.split("."), []
        while existing_dirs:
            missing_dirs.insert(0, existing_dirs.pop(-1))
            try:
                base_module = import_module(".".join(existing_dirs))
            except (ImportError, ValueError):
                continue
            else:
                try:
                    base_dir = module_dir(base_module)
                except ValueError:
                    continue
                else:
                    break
        else:
            raise ValueError(
                "Could not locate an appropriate location to create "
                "migrations package %s. Make sure the toplevel "
                "package exists and can be imported." %
                migrations_package_name)

        final_dir = os.path.join(base_dir, *missing_dirs)
        if not os.path.isdir(final_dir):
            os.makedirs(final_dir)
        for missing_dir in missing_dirs:
            base_dir = os.path.join(base_dir, missing_dir)
            with open(os.path.join(base_dir, "__init__.py"), "w"):
                pass

        return final_dir
예제 #9
0
    def serialize_db_to_string(self):
        """
        Serialize all data in the database into a JSON string.
        Designed only for test runner usage; will not handle large
        amounts of data.
        """
        # Build list of all apps to serialize
        from djmodels.db.migrations.loader import MigrationLoader
        loader = MigrationLoader(self.connection)
        app_list = []
        for app_config in apps.get_app_configs():
            if (
                app_config.models_module is not None and
                app_config.label in loader.migrated_apps and
                app_config.name not in settings.TEST_NON_SERIALIZED_APPS
            ):
                app_list.append((app_config, None))

        # Make a function to iteratively return every object
        def get_objects():
            for model in serializers.sort_dependencies(app_list):
                if (model._meta.can_migrate(self.connection) and
                        router.allow_migrate_model(self.connection.alias, model)):
                    queryset = model._default_manager.using(self.connection.alias).order_by(model._meta.pk.name)
                    yield from queryset.iterator()
        # Serialize to a string
        out = StringIO()
        serializers.serialize("json", get_objects(), indent=None, stream=out)
        return out.getvalue()
예제 #10
0
 def test_load_module_file(self):
     with override_settings(
             MIGRATION_MODULES={
                 "migrations": "migrations.faulty_migrations.file"
             }):
         loader = MigrationLoader(connection)
         self.assertIn(
             "migrations", loader.unmigrated_apps,
             "App with migrations module file not in unmigrated apps.")
예제 #11
0
 def test_load_empty_dir(self):
     with override_settings(
             MIGRATION_MODULES={
                 "migrations": "migrations.faulty_migrations.namespace"
             }):
         loader = MigrationLoader(connection)
         self.assertIn(
             "migrations", loader.unmigrated_apps,
             "App missing __init__.py in migrations module not in unmigrated apps."
         )
예제 #12
0
 def test_plan_handles_repeated_migrations(self):
     """
     _generate_plan() doesn't readd migrations already in the plan (#29180).
     """
     migration_loader = MigrationLoader(connection)
     nodes = [('migrations', '0002_second'),
              ('migrations2', '0001_initial')]
     self.assertEqual(
         migration_loader.graph._generate_plan(nodes, at_end=True),
         [('migrations', '0001_initial'), ('migrations', '0002_second'),
          ('migrations2', '0001_initial')])
예제 #13
0
 def test_loading_squashed(self):
     "Tests loading a squashed migration"
     migration_loader = MigrationLoader(connection)
     recorder = MigrationRecorder(connection)
     self.addCleanup(recorder.flush)
     # Loading with nothing applied should just give us the one node
     self.assertEqual(
         len([
             x for x in migration_loader.graph.nodes if x[0] == "migrations"
         ]),
         1,
     )
     # However, fake-apply one migration and it should now use the old two
     recorder.record_applied("migrations", "0001_initial")
     migration_loader.build_graph()
     self.assertEqual(
         len([
             x for x in migration_loader.graph.nodes if x[0] == "migrations"
         ]),
         2,
     )
예제 #14
0
 def test_name_match(self):
     "Tests prefix name matching"
     migration_loader = MigrationLoader(connection)
     self.assertEqual(
         migration_loader.get_migration_by_prefix("migrations",
                                                  "0001").name,
         "0001_initial",
     )
     with self.assertRaises(AmbiguityError):
         migration_loader.get_migration_by_prefix("migrations", "0")
     with self.assertRaises(KeyError):
         migration_loader.get_migration_by_prefix("migrations", "blarg")
예제 #15
0
    def test_load_unmigrated_dependency(self):
        """
        Makes sure the loader can load migrations with a dependency on an unmigrated app.
        """
        # Load and test the plan
        migration_loader = MigrationLoader(connection)
        self.assertEqual(
            migration_loader.graph.forwards_plan(
                ("migrations", "0001_initial")),
            [
                ('contenttypes', '0001_initial'),
                ('auth', '0001_initial'),
                ("migrations", "0001_initial"),
            ],
        )
        # Now render it out!
        project_state = migration_loader.project_state(
            ("migrations", "0001_initial"))
        self.assertEqual(
            len([m for a, m in project_state.models if a == "migrations"]), 1)

        book_state = project_state.models["migrations", "book"]
        self.assertEqual([x for x, y in book_state.fields], ["id", "user"])
예제 #16
0
 def test_first(self):
     """
     Makes sure the '__first__' migrations build correctly.
     """
     migration_loader = MigrationLoader(connection)
     self.assertEqual(
         migration_loader.graph.forwards_plan(("migrations", "second")),
         [
             ("migrations", "thefirst"),
             ("migrations2", "0001_initial"),
             ("migrations2", "0002_second"),
             ("migrations", "second"),
         ],
     )
예제 #17
0
 def test_run_before(self):
     """
     Makes sure the loader uses Migration.run_before.
     """
     # Load and test the plan
     migration_loader = MigrationLoader(connection)
     self.assertEqual(
         migration_loader.graph.forwards_plan(
             ("migrations", "0002_second")),
         [
             ("migrations", "0001_initial"),
             ("migrations", "0003_third"),
             ("migrations", "0002_second"),
         ],
     )
예제 #18
0
 def test_check_consistent_history(self):
     loader = MigrationLoader(connection=None)
     loader.check_consistent_history(connection)
     recorder = MigrationRecorder(connection)
     recorder.record_applied('migrations', '0002_second')
     msg = (
         "Migration migrations.0002_second is applied before its dependency "
         "migrations.0001_initial on database 'default'.")
     with self.assertRaisesMessage(InconsistentMigrationHistory, msg):
         loader.check_consistent_history(connection)
예제 #19
0
 def test_invalid(self):
     """
     MigrationLoader reraises ImportErrors caused by "bad magic number" pyc
     files with a more helpful message.
     """
     with self.temporary_migration_module(
             module='migrations.test_migrations_bad_pyc') as migration_dir:
         # The -tpl suffix is to avoid the pyc exclusion in MANIFEST.in.
         os.rename(
             os.path.join(migration_dir, '0001_initial.pyc-tpl'),
             os.path.join(migration_dir, '0001_initial.pyc'),
         )
         msg = (
             r"Couldn't import '\w+.migrations.0001_initial' as it appears "
             "to be a stale .pyc file.")
         with self.assertRaisesRegex(ImportError, msg):
             MigrationLoader(connection)
예제 #20
0
 def test_valid(self):
     """
     To support frozen environments, MigrationLoader loads .pyc migrations.
     """
     with self.temporary_migration_module(
             module='migrations.test_migrations') as migration_dir:
         # Compile .py files to .pyc files and delete .py files.
         compileall.compile_dir(migration_dir,
                                force=True,
                                quiet=1,
                                legacy=True)
         for name in os.listdir(migration_dir):
             if name.endswith('.py'):
                 os.remove(os.path.join(migration_dir, name))
         loader = MigrationLoader(connection)
         self.assertIn(('migrations', '0001_initial'),
                       loader.disk_migrations)
예제 #21
0
 def test_check_consistent_history_squashed(self):
     """
     MigrationLoader.check_consistent_history() should ignore unapplied
     squashed migrations that have all of their `replaces` applied.
     """
     loader = MigrationLoader(connection=None)
     recorder = MigrationRecorder(connection)
     recorder.record_applied('migrations', '0001_initial')
     recorder.record_applied('migrations', '0002_second')
     loader.check_consistent_history(connection)
     recorder.record_applied('migrations', '0003_third')
     loader.check_consistent_history(connection)
예제 #22
0
    def show_plan(self, connection, app_names=None):
        """
        Show all known migrations (or only those of the specified app_names)
        in the order they will be applied.
        """
        # Load migrations from disk/DB
        loader = MigrationLoader(connection)
        graph = loader.graph
        if app_names:
            self._validate_app_names(loader, app_names)
            targets = [
                key for key in graph.leaf_nodes() if key[0] in app_names
            ]
        else:
            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:
                    node = graph.node_map[migration]
                    plan.append(node)
                    seen.add(migration)

        # Output
        def print_deps(node):
            out = []
            for parent in sorted(node.parents):
                out.append("%s.%s" % parent.key)
            if out:
                return " ... (%s)" % ", ".join(out)
            return ""

        for node in plan:
            deps = ""
            if self.verbosity >= 2:
                deps = print_deps(node)
            if node.key in loader.applied_migrations:
                self.stdout.write("[X]  %s.%s%s" %
                                  (node.key[0], node.key[1], deps))
            else:
                self.stdout.write("[ ]  %s.%s%s" %
                                  (node.key[0], node.key[1], deps))
예제 #23
0
 def show_list(self, connection, app_names=None):
     """
     Show a list of all migrations on the system, or only those of
     some named apps.
     """
     # Load migrations from disk/DB
     loader = MigrationLoader(connection, ignore_no_migrations=True)
     graph = loader.graph
     # If we were passed a list of apps, validate it
     if app_names:
         self._validate_app_names(loader, app_names)
     # 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.ERROR)
예제 #24
0
    def test_loading_squashed_erroneous(self):
        "Tests loading a complex but erroneous set of squashed migrations"

        loader = MigrationLoader(connection)
        recorder = MigrationRecorder(connection)
        self.addCleanup(recorder.flush)

        def num_nodes():
            plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
            return len(plan - loader.applied_migrations)

        # Empty database: use squashed migration
        loader.build_graph()
        self.assertEqual(num_nodes(), 5)

        # Starting at 1 or 2 should use the squashed migration too
        recorder.record_applied("migrations", "1_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 4)

        recorder.record_applied("migrations", "2_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 3)

        # However, starting at 3 or 4, nonexistent migrations would be needed.
        msg = (
            "Migration migrations.6_auto depends on nonexistent node ('migrations', '5_auto'). "
            "Django tried to replace migration migrations.5_auto with any of "
            "[migrations.3_squashed_5] but wasn't able to because some of the replaced "
            "migrations are already applied.")

        recorder.record_applied("migrations", "3_auto")
        with self.assertRaisesMessage(NodeNotFoundError, msg):
            loader.build_graph()

        recorder.record_applied("migrations", "4_auto")
        with self.assertRaisesMessage(NodeNotFoundError, msg):
            loader.build_graph()

        # Starting at 5 to 7 we are passed the squashed migrations
        recorder.record_applied("migrations", "5_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 2)

        recorder.record_applied("migrations", "6_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 1)

        recorder.record_applied("migrations", "7_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 0)
예제 #25
0
 def test_load_import_error(self):
     with override_settings(
             MIGRATION_MODULES={"migrations": "import_error_package"}):
         with self.assertRaises(ImportError):
             MigrationLoader(connection)
예제 #26
0
    def handle(self, *app_labels, **options):
        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        self.dry_run = options['dry_run']
        self.merge = options['merge']
        self.empty = options['empty']
        self.migration_name = options['name']
        check_changes = options['check_changes']

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        has_bad_labels = False
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError as err:
                self.stderr.write(str(err))
                has_bad_labels = True
        if has_bad_labels:
            sys.exit(2)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None, ignore_no_migrations=True)

        # Raise an error if any migrations are applied before their dependencies.
        consistency_check_labels = {config.label for config in apps.get_app_configs()}
        # Non-default databases are only checked if database routers used.
        aliases_to_check = connections if settings.DATABASE_ROUTERS else [DEFAULT_DB_ALIAS]
        for alias in sorted(aliases_to_check):
            connection = connections[alias]
            if (connection.settings_dict['ENGINE'] != 'djmodels.db.backends.dummy' and any(
                    # At least one model must be migrated to the database.
                    router.allow_migrate(connection.alias, app_label, model_name=model._meta.object_name)
                    for app_label in consistency_check_labels
                    for model in apps.get_app_config(app_label).get_models()
            )):
                loader.check_consistent_history(connection)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()

        # If app_labels is specified, filter out conflicting migrations for unspecified apps
        if app_labels:
            conflicts = {
                app_label: conflict for app_label, conflict in conflicts.items()
                if app_label in app_labels
            }

        if conflicts and not self.merge:
            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 want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        if self.interactive:
            questioner = InteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run)
        else:
            questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run)
        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            questioner,
        )

        # If they want to make an empty migration, make one for each app
        if self.empty:
            if not app_labels:
                raise CommandError("You must supply at least one app label when using --empty.")
            # Make a fake changes() result we can pass to arrange_for_graph
            changes = {
                app: [Migration("custom", app)]
                for app in app_labels
            }
            changes = autodetector.arrange_for_graph(
                changes=changes,
                graph=loader.graph,
                migration_name=self.migration_name,
            )
            self.write_migration_files(changes)
            return

        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name=self.migration_name,
        )

        if not changes:
            # No changes? Tell them.
            if self.verbosity >= 1:
                if app_labels:
                    if len(app_labels) == 1:
                        self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
                    else:
                        self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
                else:
                    self.stdout.write("No changes detected")
        else:
            self.write_migration_files(changes)
            if check_changes:
                sys.exit(1)
예제 #27
0
    def test_loading_squashed_complex(self):
        "Tests loading a complex set of squashed migrations"

        loader = MigrationLoader(connection)
        recorder = MigrationRecorder(connection)
        self.addCleanup(recorder.flush)

        def num_nodes():
            plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
            return len(plan - loader.applied_migrations)

        # Empty database: use squashed migration
        loader.build_graph()
        self.assertEqual(num_nodes(), 5)

        # Starting at 1 or 2 should use the squashed migration too
        recorder.record_applied("migrations", "1_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 4)

        recorder.record_applied("migrations", "2_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 3)

        # However, starting at 3 to 5 cannot use the squashed migration
        recorder.record_applied("migrations", "3_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 4)

        recorder.record_applied("migrations", "4_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 3)

        # Starting at 5 to 7 we are passed the squashed migrations
        recorder.record_applied("migrations", "5_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 2)

        recorder.record_applied("migrations", "6_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 1)

        recorder.record_applied("migrations", "7_auto")
        loader.build_graph()
        self.assertEqual(num_nodes(), 0)
예제 #28
0
    def handle(self, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        app_label = options['app_label']
        start_migration_name = options['start_migration_name']
        migration_name = options['migration_name']
        no_optimize = options['no_optimize']
        squashed_name = options['squashed_name']
        # Validate app_label.
        try:
            apps.get_app_config(app_label)
        except LookupError as err:
            raise CommandError(str(err))
        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label
            )

        migration = self.find_migration(loader, app_label, migration_name)

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label, migration.name))
            if al == migration.app_label
        ]

        if start_migration_name:
            start_migration = self.find_migration(loader, app_label, start_migration_name)
            start = loader.get_migration(start_migration.app_label, start_migration.name)
            try:
                start_index = migrations_to_squash.index(start)
                migrations_to_squash = migrations_to_squash[start_index:]
            except ValueError:
                raise CommandError(
                    "The migration '%s' cannot be found. Maybe it comes after "
                    "the migration '%s'?\n"
                    "Have a look at:\n"
                    "  python manage.py showmigrations %s\n"
                    "to debug this issue." % (start_migration, migration, app_label)
                )

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        # We need to take all dependencies from the first migration in the list
        # as it may be 0002 depending on 0001
        first_migration = True
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations" % get_docs_version()
                )
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label or first_migration:
                    dependencies.add(dependency)
            first_migration = False

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations, migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations))
                    )

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type("Migration", (migrations.Migration,), {
            "dependencies": dependencies,
            "operations": new_operations,
            "replaces": replaces,
        })
        if start_migration_name:
            if squashed_name:
                # Use the name from --squashed-name.
                prefix, _ = start_migration.name.split('_', 1)
                name = '%s_%s' % (prefix, squashed_name)
            else:
                # Generate a name.
                name = '%s_squashed_%s' % (start_migration.name, migration.name)
            new_migration = subclass(name, app_label)
        else:
            name = '0001_%s' % (squashed_name or 'squashed_%s' % migration.name)
            new_migration = subclass(name, app_label)
            new_migration.initial = True

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "w", encoding='utf-8') as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Created new squashed migration %s" % writer.path))
            self.stdout.write("  You should commit this migration but leave the old ones in place;")
            self.stdout.write("  the new migration will be used for new installs. Once you are sure")
            self.stdout.write("  all instances of the codebase have applied the migrations you squashed,")
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write("  Your migrations contained functions that must be manually copied over,")
                self.stdout.write("  as we could not safely copy their implementation.")
                self.stdout.write("  See the comment at the top of the squashed migration for details.")