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, [])
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")
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)
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)
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")])
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")
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")])
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")]))
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")
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())
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")])
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, {})
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.' )
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"])
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"])
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"])
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")
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)
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)
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")
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")
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)
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, )
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!"))
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)
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)
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)
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)
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, )
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())
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, )
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)
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)
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)
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)