示例#1
0
 def test_same_app_no_fk_dependency(self):
     """
     Tests that a migration with a FK between two models of the same app
     does not have a dependency to itself.
     """
     # Make state
     before = self.make_project_state([])
     after = self.make_project_state([self.author_with_publisher, self.publisher])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 1)
     # Right number of actions?
     migration = changes['testapp'][0]
     self.assertEqual(len(migration.operations), 3)
     # Right actions?
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration.operations[1]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     # Third action might vanish one day if the optimizer improves.
     action = migration.operations[2]
     self.assertEqual(action.__class__.__name__, "AddField")
     # Right dependencies?
     self.assertEqual(migration.dependencies, [])
示例#2
0
 def test_many_to_many_removed_before_through_model(self):
     """
     Removing a ManyToManyField and the "through" model in the same change must remove
     the field before the model to maintain consistency.
     """
     before = self.make_project_state([self.book_with_multiple_authors_through_attribution, self.author_name, self.attribution])
     after = self.make_project_state([self.book_with_no_author, self.author_name])  # removes both the through model and ManyToMany
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['otherapp']), 1)
     # Right number of actions?
     migration = changes['otherapp'][0]
     self.assertEqual(len(migration.operations), 4)
     # Right actions in right order?
     # The first two are because we can't optimise RemoveField
     # into DeleteModel reliably.
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "RemoveField")
     self.assertEqual(action.name, "author")
     action = migration.operations[1]
     self.assertEqual(action.__class__.__name__, "RemoveField")
     self.assertEqual(action.name, "book")
     action = migration.operations[2]
     self.assertEqual(action.__class__.__name__, "RemoveField")
     self.assertEqual(action.name, "authors")
     action = migration.operations[3]
     self.assertEqual(action.__class__.__name__, "DeleteModel")
     self.assertEqual(action.name, "Attribution")
示例#3
0
def migrate_models(from_models, to_models):
    from_state = _get_state(from_models)
    to_state = _get_state(to_models)

    questioner = MigrationQuestioner()

    # Set up autodetector
    autodetector = MigrationAutodetector(from_state, to_state, questioner)

    graph = MigrationGraph()
    migrations = autodetector._detect_changes().get('indexing')
    if migrations:
        with connection.schema_editor() as schema_editor:
            for migration in migrations:
                for operation in migration.operations:
                    # Since migrations are not loaded from disk, migrations
                    # which contain fields will have their field bound to models.
                    # We need to recreate these fields as if they were loaded
                    # from disk. Thus deconstructing them and reconstructing.
                    # However Django has a shortcut for this 'clone()'.
                    if isinstance(operation, operations.CreateModel):
                        operation.fields = [
                            (field[0], field[1].clone())
                            for field in operation.fields
                        ]
                    elif isinstance(
                        operation, (
                            operations.AddField, operations.AlterField
                        )
                    ):
                        operation.field = operation.field.clone()

                    logger.info(operation.describe())
                migration.apply(from_state, schema_editor)
示例#4
0
 def test_rename_model_with_renamed_rel_field(self):
     """
     Tests autodetection of renamed models while simultaneously renaming one
     of the fields that relate to the renamed model.
     """
     # Make state
     before = self.make_project_state([self.author_with_book, self.book])
     after = self.make_project_state([self.author_renamed_with_book, self.book_with_field_and_author_renamed])
     autodetector = MigrationAutodetector(before, after, MigrationQuestioner({"ask_rename_model": True, "ask_rename": True}))
     changes = autodetector._detect_changes()
     # Right number of migrations for model rename?
     self.assertNumberMigrations(changes, 'testapp', 1)
     # Right number of actions?
     migration = changes['testapp'][0]
     self.assertEqual(len(migration.operations), 1)
     # Right actions?
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "RenameModel")
     self.assertEqual(action.old_name, "Author")
     self.assertEqual(action.new_name, "Writer")
     # Right number of migrations for related field rename?
     # Alter is already taken care of.
     self.assertNumberMigrations(changes, 'otherapp', 1)
     # Right number of actions?
     migration = changes['otherapp'][0]
     self.assertEqual(len(migration.operations), 1)
     # Right actions?
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "RenameField")
     self.assertEqual(action.old_name, "author")
     self.assertEqual(action.new_name, "writer")
    def handle(self, *args, **kwargs):

        changed = set()
        ignore_list = ['authtools']  # dependencies that we don't care about migrations for (usually for testing only)

        self.stdout.write("Checking...")
        for db in settings.DATABASES.keys():

            try:
                executor = MigrationExecutor(connections[db])
            except OperationalError:
                sys.exit("Unable to check migrations: cannot connect to database\n")

            autodetector = MigrationAutodetector(
                executor.loader.project_state(),
                ProjectState.from_apps(apps),
            )

            changed.update(autodetector.changes(graph=executor.loader.graph).keys())

        for ignore in ignore_list:
            if ignore in changed:
                changed.remove(ignore)

        if changed:
            sys.exit("Apps with model changes but no corresponding migration file: %(changed)s\n" % {
                'changed': list(changed)
            })
        else:
            sys.stdout.write("All migration files present\n")
    def test_total_deconstruct(self):
        loader = MigrationLoader(None, load=True, ignore_no_migrations=True)
        loader.disk_migrations = {t: v for t, v in loader.disk_migrations.items() if t[0] != 'testapp'}
        app_labels = {"testapp"}
        questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=True)
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            questioner,
        )
        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name="my_fake_migration_for_test_deconstruct",
        )
        self.assertGreater(len(changes), 0)
        for app_label, app_migrations in changes.items():
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)

                migration_string = writer.as_string()
                self.assertNotEqual(migration_string, "")
    def handle(self, *args, **kwargs):

        changed = set()

        self.stdout.write("Checking...")
        for db in settings.DATABASES.keys():

            try:
                executor = MigrationExecutor(connections[db])
            except OperationalError:
                self.stdout.write("Unable to check migrations: cannot connect to database '{}'.\n".format(db))
                sys.exit(1)

            all_apps = apps.app_configs.keys()
            questioner = InteractiveMigrationQuestioner(specified_apps=all_apps, dry_run=True)

            autodetector = MigrationAutodetector(
                executor.loader.project_state(),
                ProjectState.from_apps(apps),
                questioner,
            )

            changed.update(autodetector.changes(graph=executor.loader.graph, convert_apps=all_apps).keys())

        if changed:
            self.stdout.write(
                "Apps with model changes but no corresponding migration file: {!r}\n".format(
                    list(changed)
                )
            )
            sys.exit(1)
        else:
            self.stdout.write("All migration files present.\n")
            sys.exit(0)
示例#8
0
 def test_same_app_circular_fk_dependency_and_unique_together(self):
     """
     Tests that a migration with circular FK dependency does not try to
     create unique together constraint before creating all required fields first.
     See ticket #22275.
     """
     # Make state
     before = self.make_project_state([])
     after = self.make_project_state([self.knight, self.rabbit])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['eggs']), 2)
     # Right number of actions?
     migration1 = changes['eggs'][0]
     self.assertEqual(len(migration1.operations), 2)
     migration2 = changes['eggs'][1]
     self.assertEqual(len(migration2.operations), 2)
     # Right actions?
     action = migration1.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration1.operations[1]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     # CreateModel action for Rabbit should not have unique_together now
     self.assertEqual(action.name, "Rabbit")
     self.assertFalse("unique_together" in action.options)
     action = migration2.operations[0]
     self.assertEqual(action.__class__.__name__, "AddField")
     self.assertEqual(action.name, "parent")
     action = migration2.operations[1]
     self.assertEqual(action.__class__.__name__, "AlterUniqueTogether")
     self.assertEqual(action.name, "rabbit")
     # Right dependencies?
     self.assertEqual(migration1.dependencies, [])
     self.assertEqual(migration2.dependencies, [("eggs", "auto_1")])
示例#9
0
    def test_rename_model(self):
        "Tests autodetection of renamed models"
        # Make state
        before = self.make_project_state([self.author_with_book, self.book])
        after = self.make_project_state([self.author_renamed_with_book, self.book_with_author_renamed])
        autodetector = MigrationAutodetector(before, after, MigrationQuestioner({"ask_rename_model": True}))
        changes = autodetector._detect_changes()

        # Right number of migrations for model rename?
        self.assertEqual(len(changes['testapp']), 1)
        # Right number of actions?
        migration = changes['testapp'][0]
        self.assertEqual(len(migration.operations), 1)
        # Right action?
        action = migration.operations[0]
        self.assertEqual(action.__class__.__name__, "RenameModel")
        self.assertEqual(action.old_name, "Author")
        self.assertEqual(action.new_name, "Writer")

        # Right number of migrations for related field rename?
        self.assertEqual(len(changes['otherapp']), 1)
        # Right number of actions?
        migration = changes['otherapp'][0]
        self.assertEqual(len(migration.operations), 1)
        # Right action?
        action = migration.operations[0]
        self.assertEqual(action.__class__.__name__, "AlterField")
        self.assertEqual(action.name, "author")
        self.assertEqual(action.field.rel.to.__name__, "Writer")
示例#10
0
 def test_fk_dependency(self):
     "Tests that having a ForeignKey automatically adds a dependency"
     # Make state
     before = self.make_project_state([])
     after = self.make_project_state([self.author_name, self.book, self.edition])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 1)
     self.assertEqual(len(changes['otherapp']), 1)
     self.assertEqual(len(changes['thirdapp']), 1)
     # Right number of actions?
     migration1 = changes['testapp'][0]
     self.assertEqual(len(migration1.operations), 1)
     migration2 = changes['otherapp'][0]
     self.assertEqual(len(migration2.operations), 1)
     migration3 = changes['thirdapp'][0]
     self.assertEqual(len(migration3.operations), 1)
     # Right actions?
     action = migration1.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration2.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration3.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     # Right dependencies?
     self.assertEqual(migration1.dependencies, [])
     self.assertEqual(migration2.dependencies, [("testapp", "auto_1")])
     self.assertEqual(migration3.dependencies, [("otherapp", "auto_1")])
示例#11
0
 def test_circular_fk_dependency(self):
     """
     Tests that having a circular ForeignKey dependency automatically
     resolves the situation into 2 migrations on one side and 1 on the other.
     """
     # Make state
     before = self.make_project_state([])
     after = self.make_project_state([self.author_with_book, self.book])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 1)
     self.assertEqual(len(changes['otherapp']), 2)
     # Right number of actions?
     migration1 = changes['testapp'][0]
     self.assertEqual(len(migration1.operations), 1)
     migration2 = changes['otherapp'][0]
     self.assertEqual(len(migration2.operations), 1)
     migration3 = changes['otherapp'][1]
     self.assertEqual(len(migration2.operations), 1)
     # Right actions?
     action = migration1.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration2.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     self.assertEqual(len(action.fields), 2)
     action = migration3.operations[0]
     self.assertEqual(action.__class__.__name__, "AddField")
     self.assertEqual(action.name, "author")
     # Right dependencies?
     self.assertEqual(migration1.dependencies, [("otherapp", "auto_1")])
     self.assertEqual(migration2.dependencies, [])
     self.assertEqual(set(migration3.dependencies), set([("otherapp", "auto_1"), ("testapp", "auto_1")]))
示例#12
0
 def test_same_app_circular_fk_dependency(self):
     """
     Tests that a migration with a FK between two models of the same app
     does not have a dependency to itself.
     """
     # Make state
     before = self.make_project_state([])
     after = self.make_project_state([self.author_with_publisher, self.publisher_with_author])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 2)
     # Right number of actions?
     migration1 = changes['testapp'][0]
     self.assertEqual(len(migration1.operations), 2)
     migration2 = changes['testapp'][1]
     self.assertEqual(len(migration2.operations), 1)
     # Right actions?
     action = migration1.operations[0]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration1.operations[1]
     self.assertEqual(action.__class__.__name__, "CreateModel")
     action = migration2.operations[0]
     self.assertEqual(action.__class__.__name__, "AddField")
     self.assertEqual(action.name, "publisher")
     # Right dependencies?
     self.assertEqual(migration1.dependencies, [])
     self.assertEqual(migration2.dependencies, [("testapp", "auto_1")])
    def handle(self, *args, **options):
        changed = set()

        self.stdout.write("Checking...")
        for db in settings.DATABASES.keys():
            try:
                executor = MigrationExecutor(connections[db])
            except OperationalError:
                sys.exit("Unable to check migrations: cannot connect to database\n")

            autodetector = MigrationAutodetector(
                executor.loader.project_state(),
                ProjectState.from_apps(apps),
            )
            changed.update(autodetector.changes(graph=executor.loader.graph).keys())

        changed -= set(options['ignore'])

        if changed:
            sys.exit(
                "Apps with model changes but no corresponding migration file: %(changed)s\n" % {
                    'changed': list(changed)
                })
        else:
            sys.stdout.write("All migration files present\n")
示例#14
0
 def test_deconstruct_field_kwarg(self):
     """
     Field instances are handled correctly by nested deconstruction.
     """
     before = self.make_project_state([self.author_name_deconstructable_3])
     after = self.make_project_state([self.author_name_deconstructable_4])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     self.assertEqual(changes, {})
    def write_migration(self, migration):
        loader = MigrationLoader(None, ignore_no_migrations=True)
        autodetector = MigrationAutodetector(loader.project_state(), ProjectState.from_apps(apps),)
        changes = autodetector.arrange_for_graph(changes={'share': [migration]}, graph=loader.graph,)

        for m in changes['share']:
            writer = MigrationWriter(m)
            with open(writer.path, 'wb') as fp:
                fp.write(writer.as_string())
示例#16
0
 def test_swappable(self):
     before = self.make_project_state([self.custom_user])
     after = self.make_project_state([self.custom_user, self.author_with_custom_user])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes), 1)
     # Check the dependency is correct
     migration = changes['testapp'][0]
     self.assertEqual(migration.dependencies, [("__setting__", "AUTH_USER_MODEL")])
示例#17
0
 def test_custom_deconstructable(self):
     """
     Two instances which deconstruct to the same value aren't considered a
     change.
     """
     before = self.make_project_state([self.author_name_deconstructable_1])
     after = self.make_project_state([self.author_name_deconstructable_2])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     self.assertEqual(changes, {})
示例#18
0
    def test_missing_migrations(self):
        executor = MigrationExecutor(connection)
        autodetector = MigrationAutodetector(
            executor.loader.project_state(),
            ProjectState.from_apps(apps),
        )

        changes = autodetector.changes(graph=executor.loader.graph)

        self.assertEqual({}, changes)
 def test_set_alter_order_with_respect_to(self):
     "Tests that setting order_with_respect_to adds a field"
     # Make state
     before = self.make_project_state([self.book, self.author_with_book])
     after = self.make_project_state([self.book, self.author_with_book_order_wrt])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, 'testapp', 1)
     self.assertOperationTypes(changes, 'testapp', 0, ["AlterOrderWithRespectTo"])
     self.assertOperationAttributes(changes, 'testapp', 0, 0, name="author", order_with_respect_to="book")
 def test_alter_model_options_proxy(self):
     """
     Changing a proxy model's options should also make a change
     """
     before = self.make_project_state([self.author_proxy, self.author_empty])
     after = self.make_project_state([self.author_proxy_options, self.author_empty])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, "testapp", 1)
     # Right actions in right order?
     self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
 def test_for_missing_migrations(self):
     """Checks if there're models changes which aren't reflected in migrations."""
     migrations_loader = MigrationExecutor(connection).loader
     migrations_detector = MigrationAutodetector(
         from_state=migrations_loader.project_state(),
         to_state=ProjectState.from_apps(apps)
     )
     if migrations_detector.changes(graph=migrations_loader.graph):
         self.fail(
             'Your models have changes that are not yet reflected '
             'in a migration. You should add them now.'
         )
示例#22
0
 def test_create_with_through_model(self):
     """
     Adding a m2m with a through model and the models that use it should
     be ordered correctly.
     """
     before = self.make_project_state([])
     after = self.make_project_state([self.author_with_m2m_through, self.publisher, self.contract])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, "testapp", 1)
     # Right actions in right order?
     self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel", "CreateModel", "AddField", "AddField"])
示例#23
0
 def test_alter_model_options(self):
     """
     If two models with a ForeignKey from one to the other are removed at the same time,
     the autodetector should remove them in the correct order.
     """
     before = self.make_project_state([self.author_empty])
     after = self.make_project_state([self.author_with_options])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, "testapp", 1)
     # Right actions in right order?
     self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
示例#24
0
 def test_non_circular_foreignkey_dependency_removal(self):
     """
     If two models with a ForeignKey from one to the other are removed at the same time,
     the autodetector should remove them in the correct order.
     """
     before = self.make_project_state([self.author_with_publisher, self.publisher_with_author])
     after = self.make_project_state([])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, "testapp", 1)
     # Right actions in right order?
     self.assertOperationTypes(changes, "testapp", 0, ["RemoveField", "RemoveField", "DeleteModel", "DeleteModel"])
示例#25
0
 def test_many_to_many_removed_before_through_model_2(self):
     """
     Removing a model that contains a ManyToManyField and the
     "through" model in the same change must remove
     the field before the model to maintain consistency.
     """
     before = self.make_project_state([self.book_with_multiple_authors_through_attribution, self.author_name, self.attribution])
     after = self.make_project_state([self.author_name])  # removes both the through model and ManyToMany
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, 'otherapp', 1)
     # Right number of actions?
     self.assertOperationTypes(changes, 'otherapp', 0, ["RemoveField", "RemoveField", "RemoveField", "DeleteModel", "DeleteModel"])
 def test_remove_alter_order_with_respect_to(self):
     """
     Tests that removing order_with_respect_to when removing the FK too
     does things in the right order.
     """
     # Make state
     before = self.make_project_state([self.book, self.author_with_book_order_wrt])
     after = self.make_project_state([self.author_name])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertNumberMigrations(changes, 'testapp', 1)
     self.assertOperationTypes(changes, 'testapp', 0, ["AlterOrderWithRespectTo", "RemoveField"])
     self.assertOperationAttributes(changes, 'testapp', 0, 0, name="author", order_with_respect_to=None)
     self.assertOperationAttributes(changes, 'testapp', 0, 1, model_name="author", name="book")
示例#27
0
    def test_django_17_migrations(self):

        from django.apps import apps
        from django.db.migrations.loader import MigrationLoader
        from django.db.migrations.autodetector import MigrationAutodetector
        from django.db.migrations.state import ProjectState
        from django.db.migrations.questioner import MigrationQuestioner
        app_labels = set(app.label for app in apps.get_app_configs()
                         if app.name.startswith('wagtail.'))
        for app_label in app_labels:
            apps.get_app_config(app_label.split('.')[-1])
        loader = MigrationLoader(None, ignore_no_migrations=True)

        conflicts = dict(
            (app_label, conflict)
            for app_label, conflict in iteritems(loader.detect_conflicts())
            if app_label in app_labels
        )

        if conflicts:
            name_str = "; ".join("%s in %s" % (", ".join(names), app)
                                 for app, names in conflicts.items())
            self.fail("Conflicting migrations detected (%s)." % name_str)

        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            MigrationQuestioner(specified_apps=app_labels, dry_run=True),
        )

        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
        )

        if changes:
            apps = ', '.join(apps.get_app_config(label).name
                             for label in changes.keys())
            migrations = '\n'.join((
                '  {migration}\n{changes}'.format(
                    migration=migration,
                    changes='\n'.join('    {0}'.format(operation.describe())
                                      for operation in migration.operations))
                for (_, migrations) in changes.items()
                for migration in migrations))

            self.fail('Model changes with no migrations detected:\n%s' % migrations)
示例#28
0
 def test_trim_apps(self):
     "Tests that trim does not remove dependencies but does remove unwanted apps"
     # Use project state to make a new migration change set
     before = self.make_project_state([])
     after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable, self.third_thing])
     autodetector = MigrationAutodetector(before, after, MigrationQuestioner(defaults={"ask_initial": True}))
     changes = autodetector._detect_changes()
     # Run through arrange_for_graph
     graph = MigrationGraph()
     changes = autodetector.arrange_for_graph(changes, graph)
     changes["testapp"][0].dependencies.append(("otherapp", "0001_initial"))
     changes = autodetector._trim_to_apps(changes, set(["testapp"]))
     # Make sure there's the right set of migrations
     self.assertEqual(changes["testapp"][0].name, "0001_initial")
     self.assertEqual(changes["otherapp"][0].name, "0001_initial")
     self.assertNotIn("thirdapp", changes)
示例#29
0
 def test_old_model(self):
     "Tests deletion of old models"
     # Make state
     before = self.make_project_state([self.author_empty])
     after = self.make_project_state([])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 1)
     # Right number of actions?
     migration = changes['testapp'][0]
     self.assertEqual(len(migration.operations), 1)
     # Right action?
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "DeleteModel")
     self.assertEqual(action.name, "Author")
示例#30
0
 def test_alter_field(self):
     "Tests autodetection of new fields"
     # Make state
     before = self.make_project_state([self.author_name])
     after = self.make_project_state([self.author_name_longer])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Right number of migrations?
     self.assertEqual(len(changes['testapp']), 1)
     # Right number of actions?
     migration = changes['testapp'][0]
     self.assertEqual(len(migration.operations), 1)
     # Right action?
     action = migration.operations[0]
     self.assertEqual(action.__class__.__name__, "AlterField")
     self.assertEqual(action.name, "name")
示例#31
0
    def handle(self, *app_labels, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)
        self.merge = options.get('merge', False)
        self.empty = options.get('empty', False)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                    app_label)
            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)

        # 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 = dict((app_label, conflict)
                             for app_label, conflict in iteritems(conflicts)
                             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 (%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)

        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=app_labels,
                                           dry_run=self.dry_run),
        )

        # 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 = dict(
                (app, [Migration("custom", app)]) for app in app_labels)
            changes = autodetector.arrange_for_graph(changes, loader.graph)
            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,
        )

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" %
                                  app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" %
                                  ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        self.write_migration_files(changes)
示例#32
0
    def handle(self, *args, **options):

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

        # @@ TODO あとで。
        # managementを読んで何をしているのか。
        # 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)

        logger.debug('options {}'.format(options))
        # @@ 普通は`default`が入っている
        db = options['database']
        # @@ 対応するDBへのコネクションラッパを取得する
        # ConnectionHandler().__getitem__(db)
        # django.db.backends.sqlite3.base.DatabaseWrapper が戻る
        connection = connections[db]
        logger.debug('connection {}'.format(connection))

        # @@ sqlite3では未実装なのでpass
        # 実際のところgisだけっぽい
        # postgisのbaseより。
        #     def prepare_database(self):
        #         super().prepare_database()
        #         # Check that postgis extension is installed.
        #         with self.cursor() as cursor:
        #             cursor.execute("CREATE EXTENSION IF NOT EXISTS postgis")
        connection.prepare_database()

        # Work out which apps have migrations and which do not
        # @@ executorの取得
        # migration_progress_callbackは進捗をstdoutにいい感じにだすための処理
        # DBから状態を取り出し、適用をするクラス
        executor = MigrationExecutor(connection, self.migration_progress_callback)

        # Raise an error if any migrations are applied before their dependencies.
        # @@
        # executor.loaderはMigrationLoader
        # 適用済マイグレーションのツリーの一貫性チェック
        # 適用されているマイグレーションのparentがgraph.nodesにあるかを見ている
        # どこかで辿りきれなくなっているのはまずいので。どこかで流れが変わっている可能性がある
        executor.loader.check_consistent_history(connection)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any
        # @@ マイグレーションファイルのコンフリクトチェック
        # 同じAppで複数のリーフが存在していないかを見ている
        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.
        # @@ app_labelやmigration_nameの対応
        # 該当する部分だけをdisk_migrationから取り出す
        # 未指定なら全部の末端ノードを取り出す
        target_app_labels_only = True
        if options['app_label'] and options['migration_name']:
            app_label, migration_name = options['app_label'], options['migration_name']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations." % app_label
                )
            if migration_name == "zero":
                targets = [(app_label, None)]
            else:
                try:
                    # @@
                    # migration_nameは前方一致で単一になればなんでもいい
                    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']:
            app_label = options['app_label']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations." % app_label
                )
            targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label]
        else:
            targets = executor.loader.graph.leaf_nodes()

        logger.debug('migration target {}'.format(targets))
        # @@
        # 実際に適用すべきマイグレーションを決定する
        plan = executor.migration_plan(targets)
        logger.debug('plan {}'.format(plan))
        # @@
        # https://docs.djangoproject.com/en/2.0/ref/django-admin/#django-admin-migrate
        # --run-syncdb¶
        # Allows creating tables for apps without migrations. While this isn’t recommended,
        # the migrations framework is sometimes too slow on large projects with hundreds of models.
        # これのためにあるらしい
        run_syncdb = options['run_syncdb'] and executor.loader.unmigrated_apps
        logger.debug('executor.loader.unmigrated_apps {}'.format(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])
                    )

        # @@ マイグレーション前のProjectStateを構成する
        # 恐らく適用済の範囲だけっぽい
        pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
        pre_migrate_apps = pre_migrate_state.apps
        # @@ シグナルを投げる
        # 最終的にinject_rename_contenttypes_operationsが呼ばれる
        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.
                # @@ これは優しさ
                # もしmakemigrationsされてない変更があれば警告する
                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']
        # @@ migrateを実行する
        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,
        )
示例#33
0
    def handle(self, *args, **options):
        self.model_name = options['model_name']
        self.source_app = options['source_app']
        self.dest_app = options['dest_app']

        # make sure the apps exist
        app_labels = set([self.source_app, self.dest_app])
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    self.style.ERROR(
                        "App '{}' could not be found. Is it in INSTALLED_APPS?"
                        .format(app_label)))
            sys.exit(2)

        if len(app_labels) == 1:
            self.stderr.write(
                self.style.ERROR(
                    "Cannot move '{}' within the same app '{}'.".format(
                        self.model_name, self.dest_app)))
            sys.exit(2)

        # load the current graph
        loader = MigrationLoader(None, ignore_no_migrations=True)

        questioner = NonInteractiveMigrationQuestioner()

        self.from_state = loader.project_state()
        self.to_state = ProjectState.from_apps(apps)

        autodetector = MigrationAutodetector(
            self.from_state,
            self.to_state,
            questioner,
        )

        _migrations = []
        rename_table = self._get_rename_table_migration()
        _migrations.append(rename_table)
        create_model = self._get_create_model_migration([
            (rename_table.app_label, rename_table.name),
        ])
        _migrations.append(create_model)
        model_fk = self._get_model_fk_migrations([
            (create_model.app_label, create_model.name),
        ])
        delete_model_deps = [
            (rename_table.app_label, rename_table.name),
            (create_model.app_label, create_model.name),
        ]
        for fk_migration in model_fk:
            _migrations.append(fk_migration)
            delete_model_deps.append(
                (fk_migration.app_label, fk_migration.name), )
        delete_model = self._get_delete_model_migration(delete_model_deps)
        _migrations.append(delete_model)

        changes = {}
        for migration in _migrations:
            changes.setdefault(migration.app_label, []).append(migration)
        changes = autodetector.arrange_for_graph(
            changes=changes,
            graph=loader.graph,
        )
        self.write_migration_files(changes)

        self.stdout.write(self.style.SUCCESS("Done!"))
示例#34
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)
        self.merge = options.get('merge', False)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])

        # 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 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 (%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)

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, app_migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write("  %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                if not self.dry_run:
                    migrations_directory = os.path.dirname(writer.path)
                    if not directory_created.get(app_label, False):
                        if not os.path.isdir(migrations_directory):
                            os.mkdir(migrations_directory)
                        init_path = os.path.join(migrations_directory, "__init__.py")
                        if not os.path.isfile(init_path):
                            open(init_path, "w").close()
                        # We just do this once per app
                        directory_created[app_label] = True
                    migration_string = writer.as_string()
                    with open(writer.path, "wb") as fh:
                        fh.write(migration_string)
示例#35
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'] and options['migration_name']:
            app_label, migration_name = options['app_label'], options[
                'migration_name']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError("App '%s' does not have migrations." %
                                   app_label)
            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']:
            app_label = options['app_label']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError("App '%s' does not have migrations." %
                                   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)
        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(set(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]))

        emit_pre_migrate_signal(self.verbosity, self.interactive,
                                connection.alias)

        # 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:
            executor.check_replacements()
            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."))
        else:
            fake = options['fake']
            fake_initial = options['fake_initial']
            executor.migrate(targets,
                             plan,
                             fake=fake,
                             fake_initial=fake_initial)

        # 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)
示例#36
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                cache.get_app(app_label)
            except ImproperlyConfigured:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                    app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_app_cache(cache),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph,
                                       trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" %
                                  app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" %
                                  ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Migrations for '%s':" %
                                               app_label) + "\n")
            for migration in migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write(
                        "  %s:\n" %
                        (self.style.MIGRATE_LABEL(writer.filename), ))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                migrations_directory = os.path.dirname(writer.path)
                if not directory_created.get(app_label, False):
                    if not os.path.isdir(migrations_directory):
                        os.mkdir(migrations_directory)
                    init_path = os.path.join(migrations_directory,
                                             "__init__.py")
                    if not os.path.isfile(init_path):
                        open(init_path, "w").close()
                    # We just do this once per app
                    directory_created[app_label] = True
                migration_string = writer.as_string()
                with open(writer.path, "wb") as fh:
                    fh.write(migration_string)
示例#37
0
    def handle(self, *args, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.show_traceback = options.get('traceback')
        self.load_initial_data = options.get('load_initial_data')
        self.test_database = options.get('test_database', False)

        # 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.get('database')
        connection = connections[db]

        # If they asked for a migration listing, quit main execution flow and show it
        if options.get("list", False):
            return self.show_migration_list(connection, args)

        # Work out which apps have migrations and which do not
        executor = MigrationExecutor(connection, self.migration_progress_callback)

        # 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 (%s).\nTo fix them run 'python manage.py makemigrations --merge'" % name_str)

        # If they supplied command line arguments, work out what they mean.
        run_syncdb = False
        target_app_labels_only = True
        if len(args) > 2:
            raise CommandError("Too many command-line arguments (expecting 'app_label' or 'app_label migrationname')")
        elif len(args) == 2:
            app_label, migration_name = args
            if app_label not in executor.loader.migrated_apps:
                raise CommandError("App '%s' does not have migrations (you cannot selectively sync unmigrated apps)" % app_label)
            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." % (app_label, migration_name))
                except KeyError:
                    raise CommandError("Cannot find a migration matching '%s' from app '%s'." % (app_label, migration_name))
                targets = [(app_label, migration.name)]
            target_app_labels_only = False
        elif len(args) == 1:
            app_label = args[0]
            if app_label not in executor.loader.migrated_apps:
                raise CommandError("App '%s' does not have migrations (you cannot selectively sync unmigrated apps)" % app_label)
            targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label]
        else:
            targets = executor.loader.graph.leaf_nodes()
            run_syncdb = True

        plan = executor.migration_plan(targets)

        # 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(executor.loader.unmigrated_apps) or "(none)"))
            if target_app_labels_only:
                self.stdout.write(self.style.MIGRATE_LABEL("  Apply all migrations: ") + (", ".join(set(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]))

        # Run the syncdb phase.
        # If you ever manage to get rid of this, I owe you many, many drinks.
        # Note that pre_migrate is called from inside here, as it needs
        # the list of models about to be installed.
        if run_syncdb:
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:"))
            created_models = self.sync_apps(connection, executor.loader.unmigrated_apps)
        else:
            created_models = []

        # 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 needed.")
                # If there's changes that aren't in migrations yet, tell them how to fix it.
                autodetector = MigrationAutodetector(
                    executor.loader.graph.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."))
        else:
            executor.migrate(targets, plan, fake=options.get("fake", False))

        # Send the post_migrate signal, so individual apps can do whatever they need
        # to do at this point.
        emit_post_migrate_signal(created_models, self.verbosity, self.interactive, connection.alias)
示例#38
0
    def handle(self, *args, **options):
        # self 是「命令处理对象」
        print('【django.core.management.commands.migrate.Command.handle】args:',
              args)
        #print('【django.core.management.commands.migrate.Command.handle】options:' )
        #for k, v in options.items():
        #    print(f'\t{k:<22}{v}')
        database = options['database']  # 默认值是 'default'
        if not options['skip_checks']:
            # 此方法定义在父类 django.core.management.base.BaseCommand 中
            self.check(databases=[database])

        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)

        # connections 是 django.db.utils.ConnectionHandler 类的实例,叫做「数据库连接处理对象」
        # 此处调用其 __getitem__ 方法,返回的是「数据库包装对象」
        # 以 MySQL 为例,其所属类定义在 django.db.backends.mysql.base 模块中
        # 所以下面的 connection 是 django.db.backends.mysql.base.DatabaseWrapper 类的实例
        connection = connections[database]
        print(
            '【django.core.management.commands.migrate.Command.handle】connection:',
            connection)

        # 此方法定义在实例的父类中,是空函数
        connection.prepare_database()

        # 下面这十几行代码用于处理数据版本控制相关的事情

        # 此 MigrationExecutor 类定义在 django.db.migrations.executor 模块中,其实例是「数据库版本迁移执行器」
        # 实例化时提供两个参数,它们分别会被赋值给实例的同名属性:
        # connection 是「数据库包装对象」,这是位置参数
        # process_callback 是可选参数,这里提供的是定义在当前类中的方法
        executor = MigrationExecutor(connection,
                                     self.migration_progress_callback)

        # executor 是「数据库版本迁移执行器」
        # executor.loader 是 django.db.migrations.loader.MigrationLoader 类的实例
        # 该实例的 connection 属性值就是 self
        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.
        run_syncdb = options['run_syncdb']
        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 run_syncdb:
                if app_label in executor.loader.migrated_apps:
                    raise CommandError(
                        "Can't use run_syncdb with app '%s' as it has migrations."
                        % app_label)
            elif 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:
            # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例
            # 叫做「数据库版本迁移执行器」
            # executor.loader 是 django.db.migrations.loader.MigrationLoader 类的实例
            # 此实例的 graph 属性值是 django.db.migrations.graph.MigrationGraph 类的实例
            # 调用其 leaf_nodes 获得各个应用中最新版本的迁移文件名
            # targets 是列表,里面是二元元组,类似这样:
            # [('admin', '0003_logentry_add_action_flag_choices'), ...]
            targets = executor.loader.graph.leaf_nodes()
            #print('【django.core.management.commands.migrate.Command.handle】targets:')
            #for i in targets:
            #    print('\t', i)

        # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例
        # 叫做「数据库版本迁移执行器」
        plan = executor.migration_plan(targets)
        exit_dry = plan and options['check_unapplied']

        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)
            if exit_dry:
                sys.exit(1)
            return
        if exit_dry:
            sys.exit(1)

        # At this point, ignore run_syncdb if there aren't any apps to sync.
        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:
                if options['app_label']:
                    self.stdout.write(
                        self.style.MIGRATE_LABEL(
                            "  Synchronize unmigrated app: %s" % app_label))
                else:
                    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: ')
                        + str(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:"))
            if options['app_label']:
                self.sync_apps(connection, [app_label])
            else:
                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']
        # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例,叫做「数据库版本迁移执行器」
        # 参数:
        # targets 列表,里面是类似 ('auth', '0012_alter_user_first_name_max_length') 这样的元组
        # plan 列表,里面是类似 (<Migration auth.0001_initial>, False) 这样的元组
        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,
        )
示例#39
0
 def handle_merge(self, loader, conflicts):
     """
     Handles merging together conflicted migrations interactively,
     if it's safe; otherwise, advises on how to fix it.
     """
     if self.interactive:
         questioner = InteractiveMigrationQuestioner()
     else:
         questioner = MigrationQuestioner(defaults={'ask_merge': True})
     for app_label, migration_names in conflicts.items():
         # Grab out the migrations in question, and work out their
         # common ancestor.
         merge_migrations = []
         for migration_name in migration_names:
             migration = loader.get_migration(app_label, migration_name)
             migration.ancestry = loader.graph.forwards_plan(
                 (app_label, migration_name))
             merge_migrations.append(migration)
         common_ancestor = None
         for level in zip(*[m.ancestry for m in merge_migrations]):
             if reduce(operator.eq, level):
                 common_ancestor = level[0]
             else:
                 break
         if common_ancestor is None:
             raise ValueError("Could not find common ancestor of %s" %
                              migration_names)
         # Now work out the operations along each divergent branch
         for migration in merge_migrations:
             migration.branch = migration.ancestry[(
                 migration.ancestry.index(common_ancestor) + 1):]
             migration.merged_operations = []
             for node_app, node_name in migration.branch:
                 migration.merged_operations.extend(
                     loader.get_migration(node_app, node_name).operations)
         # In future, this could use some of the Optimizer code
         # (can_optimize_through) to automatically see if they're
         # mergeable. For now, we always just prompt the user.
         if self.verbosity > 0:
             self.stdout.write(
                 self.style.MIGRATE_HEADING("Merging %s" % app_label))
             for migration in merge_migrations:
                 self.stdout.write(
                     self.style.MIGRATE_LABEL("  Branch %s" %
                                              migration.name))
                 for operation in migration.merged_operations:
                     self.stdout.write("    - %s\n" % operation.describe())
         if questioner.ask_merge(app_label):
             # If they still want to merge it, then write out an empty
             # file depending on the migrations needing merging.
             numbers = [
                 MigrationAutodetector.parse_number(migration.name)
                 for migration in merge_migrations
             ]
             try:
                 biggest_number = max([x for x in numbers if x is not None])
             except ValueError:
                 biggest_number = 1
             subclass = type(
                 "Migration", (Migration, ), {
                     "dependencies": [(app_label, migration.name)
                                      for migration in merge_migrations],
                 })
             new_migration = subclass("%04i_merge" % (biggest_number + 1),
                                      app_label)
             writer = MigrationWriter(new_migration)
             with open(writer.path, "wb") as fh:
                 fh.write(writer.as_string())
             if self.verbosity > 0:
                 self.stdout.write("\nCreated new merge migration %s" %
                                   writer.path)
    def handle_merge(self, loader, conflicts):
        """
        Handles merging together conflicted migrations interactively,
        if it's safe; otherwise, advises on how to fix it.
        """
        if self.interactive:
            questioner = InteractiveMigrationQuestioner()
        else:
            questioner = MigrationQuestioner(defaults={'ask_merge': True})

        for app_label, migration_names in conflicts.items():
            # Grab out the migrations in question, and work out their
            # common ancestor.
            merge_migrations = []
            for migration_name in migration_names:
                migration = loader.get_migration(app_label, migration_name)
                migration.ancestry = [
                    mig for mig in loader.graph.forwards_plan((app_label,
                                                               migration_name))
                    if mig[0] == migration.app_label
                ]
                merge_migrations.append(migration)

            def all_items_equal(seq):
                return all(item == seq[0] for item in seq[1:])

            merge_migrations_generations = zip(*(m.ancestry
                                                 for m in merge_migrations))
            common_ancestor_count = sum(
                1 for common_ancestor_generation in takewhile(
                    all_items_equal, merge_migrations_generations))
            if not common_ancestor_count:
                raise ValueError("Could not find common ancestor of %s" %
                                 migration_names)
            # Now work out the operations along each divergent branch
            for migration in merge_migrations:
                migration.branch = migration.ancestry[common_ancestor_count:]
                migrations_ops = (loader.get_migration(node_app,
                                                       node_name).operations
                                  for node_app, node_name in migration.branch)
                migration.merged_operations = sum(migrations_ops, [])
            # In future, this could use some of the Optimizer code
            # (can_optimize_through) to automatically see if they're
            # mergeable. For now, we always just prompt the user.
            if self.verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Merging %s" % app_label))
                for migration in merge_migrations:
                    self.stdout.write(
                        self.style.MIGRATE_LABEL("  Branch %s" %
                                                 migration.name))
                    for operation in migration.merged_operations:
                        self.stdout.write("    - %s\n" % operation.describe())
            if questioner.ask_merge(app_label):
                # If they still want to merge it, then write out an empty
                # file depending on the migrations needing merging.
                numbers = [
                    MigrationAutodetector.parse_number(migration.name)
                    for migration in merge_migrations
                ]
                try:
                    biggest_number = max(x for x in numbers if x is not None)
                except ValueError:
                    biggest_number = 1
                subclass = type(
                    "Migration", (Migration, ), {
                        "dependencies": [(app_label, migration.name)
                                         for migration in merge_migrations],
                    })
                migration_name = "%04i_%s" % (
                    biggest_number + 1, self.migration_name or
                    ("merge_%s" % get_migration_name_timestamp()))
                new_migration = subclass(migration_name, app_label)
                writer = MigrationWriter(new_migration)

                if not self.dry_run:
                    # Write the merge migrations file to the disk
                    with open(writer.path, "w", encoding='utf-8') as fh:
                        fh.write(writer.as_string())
                    if self.verbosity > 0:
                        self.stdout.write("\nCreated new merge migration %s" %
                                          writer.path)
                elif self.verbosity == 3:
                    # Alternatively, makemigrations --merge --dry-run --verbosity 3
                    # will output the merge migrations to stdout rather than saving
                    # the file to the disk.
                    self.stdout.write(
                        self.style.MIGRATE_HEADING(
                            "Full merge migrations file '%s':" %
                            writer.filename) + "\n")
                    self.stdout.write("%s\n" % writer.as_string())
示例#41
0
    def handle(self, *args, **options):
        database = options['database']
        if not options['skip_checks']:
            self.check(databases=[database])

        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
        connection = connections[database]

        # 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.
        run_syncdb = options['run_syncdb']
        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 run_syncdb:
                if app_label in executor.loader.migrated_apps:
                    raise CommandError(
                        "Can't use run_syncdb with app '%s' as it has migrations."
                        % app_label)
            elif 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)
        exit_dry = plan and options['check_unapplied']

        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)
            if exit_dry:
                sys.exit(1)
            return
        if exit_dry:
            sys.exit(1)

        # At this point, ignore run_syncdb if there aren't any apps to sync.
        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:
                if options['app_label']:
                    self.stdout.write(
                        self.style.MIGRATE_LABEL(
                            "  Synchronize unmigrated app: %s" % app_label))
                else:
                    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: ')
                        + str(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:"))
            if options['app_label']:
                self.sync_apps(connection, [app_label])
            else:
                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 in app(s): %s have changes that are not "
                            "yet reflected in a migration, and so won't be "
                            "applied." %
                            ", ".join(repr(app) for app in sorted(changes))))
                    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,
        )
示例#42
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']
        if self.migration_name and not self.migration_name.isidentifier():
            raise CommandError(
                'The migration name must be a valid Python identifier.')
        self.include_header = options['include_header']
        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'] !=
                    'django.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)
示例#43
0
    def handle(self, *args, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('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.get('database')
        connection = connections[db]

        # If they asked for a migration listing, quit main execution flow and show it
        if options.get("list", False):
            warnings.warn(
                "The 'migrate --list' command is deprecated. Use 'showmigrations' instead.",
                RemovedInDjango20Warning, stacklevel=2)
            self.stdout.ending = None  # Remove when #21429 is fixed
            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=self.stdout,
                traceback=options.get('traceback'),
                verbosity=self.verbosity,
            )

        # 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)

        # 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 (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str
            )

        # If they supplied command line arguments, work out what they mean.
        run_syncdb = False
        target_app_labels_only = True
        if options['app_label'] and options['migration_name']:
            app_label, migration_name = options['app_label'], options['migration_name']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations (you cannot selectively "
                    "sync unmigrated apps)" % app_label
                )
            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']:
            app_label = options['app_label']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations (you cannot selectively "
                    "sync unmigrated apps)" % app_label
                )
            targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label]
        else:
            targets = executor.loader.graph.leaf_nodes()
            run_syncdb = True

        plan = executor.migration_plan(targets)

        # Print some useful info
        if self.verbosity >= 1:
            self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:"))
            if run_syncdb and executor.loader.unmigrated_apps:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Synchronize unmigrated apps: ") +
                    (", ".join(executor.loader.unmigrated_apps))
                )
            if target_app_labels_only:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Apply all migrations: ") +
                    (", ".join(set(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])
                    )

        emit_pre_migrate_signal(self.verbosity, self.interactive, connection.alias)

        # Run the syncdb phase.
        if run_syncdb and executor.loader.unmigrated_apps:
            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."
                    ))
        else:
            executor.migrate(targets, plan, fake=options.get("fake", False))

        # 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)
示例#44
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']
        self.exit_code = options['exit_code']
        check_changes = options['check_changes']

        if self.exit_code:
            warnings.warn(
                "The --exit option is deprecated in favor of the --check option.",
                RemovedInDjango20Warning)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                    app_label)
            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.
        for db in connections:
            loader.check_consistent_history(connections[db])

        # 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 iteritems(conflicts)
                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 len(app_labels) == 1:
                    self.stdout.write("No changes detected in app '%s'" %
                                      app_labels.pop())
                elif len(app_labels) > 1:
                    self.stdout.write("No changes detected in apps '%s'" %
                                      ("', '".join(app_labels)))
                else:
                    self.stdout.write("No changes detected")

            if self.exit_code:
                sys.exit(1)
        else:
            self.write_migration_files(changes)
            if check_changes:
                sys.exit(1)
示例#45
0
    def handle(self, *args, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')
        self.show_traceback = options.get('traceback')
        self.load_initial_data = options.get('load_initial_data')
        db_dry_run = options.get("db_dry_run")

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

        # If they asked for a migration listing, quit main execution flow and show it
        if options.get("list", False):
            self.stderr.write(
                "The 'migrate --list' command is deprecated. Use 'showmigrations' instead."
            )
            self.stdout.ending = None  # Remove when #21429 is fixed
            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=self.stdout,
                traceback=self.show_traceback,
                verbosity=self.verbosity,
            )

        # 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)

        # 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 (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str)

        # If they supplied command line arguments, work out what they mean.
        run_syncdb = False
        target_app_labels_only = True
        if options['app_label'] and options['migration_name']:
            app_label, migration_name = options['app_label'], options[
                'migration_name']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations (you cannot selectively "
                    "sync unmigrated apps)" % app_label)
            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']:
            app_label = options['app_label']
            if app_label not in executor.loader.migrated_apps:
                raise CommandError(
                    "App '%s' does not have migrations (you cannot selectively "
                    "sync unmigrated apps)" % app_label)
            targets = [
                key for key in executor.loader.graph.leaf_nodes()
                if key[0] == app_label
            ]
        else:
            targets = executor.loader.graph.leaf_nodes()
            run_syncdb = True

        plan = executor.migration_plan(targets)

        # Print some useful info
        if self.verbosity >= 1:
            self.stdout.write(
                self.style.MIGRATE_HEADING("Operations to perform:"))
            if run_syncdb and executor.loader.unmigrated_apps:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Synchronize unmigrated apps: ")
                    + (", ".join(executor.loader.unmigrated_apps)))
            if target_app_labels_only:
                self.stdout.write(
                    self.style.MIGRATE_LABEL("  Apply all migrations: ") +
                    (", ".join(set(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]))

        # Run the syncdb phase.
        # If you ever manage to get rid of this, I owe you many, many drinks.
        # Note that pre_migrate is called from inside here, as it needs
        # the list of models about to be installed.
        if run_syncdb and executor.loader.unmigrated_apps:
            if self.verbosity >= 1:
                self.stdout.write(
                    self.style.MIGRATE_HEADING(
                        "Synchronizing apps without migrations:"))
            created_models = self.sync_apps(connection,
                                            executor.loader.unmigrated_apps)
        else:
            created_models = []
            emit_pre_migrate_signal([], self.verbosity, self.interactive,
                                    connection.alias)

        # The test runner requires us to flush after a syncdb but before migrations,
        # so do that here.
        if options.get("test_flush", False):
            call_command(
                'flush',
                verbosity=max(self.verbosity - 1, 0),
                interactive=False,
                database=db,
                reset_sequences=False,
                inhibit_post_migrate=True,
            )

        # Migrate!
        if self.verbosity >= 1:
            if db_dry_run:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Running migrations dry-run:"))
            else:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Running migrations:"))
        if not plan:
            executor.check_replacements()
            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."))
        else:
            fake = options.get("fake")
            fake_initial = options.get("fake_initial")
            if db_dry_run:
                # Print the SQL without making changes if db_dry_run is set
                sql_statements = executor.collect_sql(plan)
                return '\n'.join(sql_statements)
            else:
                executor.migrate(targets,
                                 plan,
                                 fake=fake,
                                 fake_initial=fake_initial)

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