diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 8f414d38a0..30286b7aeb 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -216,7 +216,7 @@ class Command(BaseCommand): # to do at this point. emit_post_migrate_signal(created_models, self.verbosity, self.interactive, connection.alias) - def migration_progress_callback(self, action, migration, fake=False): + def migration_progress_callback(self, action, migration=None, fake=False): if self.verbosity >= 1: compute_time = self.verbosity > 1 if action == "apply_start": @@ -241,6 +241,14 @@ class Command(BaseCommand): self.stdout.write(self.style.MIGRATE_SUCCESS(" FAKED" + elapsed)) else: self.stdout.write(self.style.MIGRATE_SUCCESS(" OK" + elapsed)) + elif action == "render_start": + if compute_time: + self.start = time.time() + self.stdout.write(" Rendering model states...", ending="") + self.stdout.flush() + elif action == "render_success": + elapsed = " (%.3fs)" % (time.time() - self.start) if compute_time else "" + self.stdout.write(self.style.MIGRATE_SUCCESS(" DONE" + elapsed)) def sync_apps(self, connection, app_labels): "Runs the old syncdb-style operation on a list of app_labels." diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py index c9ef387d58..cc38758f5a 100644 --- a/django/db/migrations/executor.py +++ b/django/db/migrations/executor.py @@ -77,7 +77,11 @@ class MigrationExecutor(object): # if the migration is being run. states = {} state = ProjectState(real_apps=list(self.loader.unmigrated_apps)) + if self.progress_callback: + self.progress_callback("render_start") state.apps # Render all real_apps -- performance critical + if self.progress_callback: + self.progress_callback("render_success") # Phase 1 -- Store all required states for migration, _ in full_plan: if migration in migrations_to_run: diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index 9f8b2282c0..12385ac7da 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -150,7 +150,7 @@ class ExecutorTests(MigrationTestBase): """ state = {"faked": None} - def fake_storer(phase, migration, fake): + def fake_storer(phase, migration=None, fake=None): state["faked"] = fake executor = MigrationExecutor(connection, progress_callback=fake_storer) # Were the tables there before? @@ -323,6 +323,51 @@ class ExecutorTests(MigrationTestBase): self.assertTableNotExists("lookuperror_b_b1") self.assertTableNotExists("lookuperror_c_c1") + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) + def test_process_callback(self): + """ + #24129 - Tests callback process + """ + call_args_list = [] + + def callback(*args): + call_args_list.append(args) + + executor = MigrationExecutor(connection, progress_callback=callback) + # Were the tables there before? + self.assertTableNotExists("migrations_author") + self.assertTableNotExists("migrations_tribble") + executor.migrate([ + ("migrations", "0001_initial"), + ("migrations", "0002_second"), + ]) + # Rebuild the graph to reflect the new DB state + executor.loader.build_graph() + + executor.migrate([ + ("migrations", None), + ("migrations", None), + ]) + self.assertTableNotExists("migrations_author") + self.assertTableNotExists("migrations_tribble") + + migrations = executor.loader.graph.nodes + expected = [ + ("render_start", ), + ("render_success", ), + ("apply_start", migrations['migrations', '0001_initial'], False), + ("apply_success", migrations['migrations', '0001_initial'], False), + ("apply_start", migrations['migrations', '0002_second'], False), + ("apply_success", migrations['migrations', '0002_second'], False), + ("render_start", ), + ("render_success", ), + ("unapply_start", migrations['migrations', '0002_second'], False), + ("unapply_success", migrations['migrations', '0002_second'], False), + ("unapply_start", migrations['migrations', '0001_initial'], False), + ("unapply_success", migrations['migrations', '0001_initial'], False), + ] + self.assertEqual(call_args_list, expected) + class FakeLoader(object): def __init__(self, graph, applied):