[Python-modules-commits] [python-django] 01/01: Accept again migrations depending on initial migrations that can be fake applied
Raphaël Hertzog
hertzog at moszumanska.debian.org
Fri Jun 2 09:44:47 UTC 2017
This is an automated email from the git hooks/post-receive script.
hertzog pushed a commit to branch debian/master
in repository python-django.
commit 53f0ab6240f3db21d0a770511081ab503dad669b
Author: Raphaël Hertzog <hertzog at debian.org>
Date: Mon May 29 17:00:50 2017 +0200
Accept again migrations depending on initial migrations that can be fake applied
Closes: #863267
---
debian/changelog | 7 +
debian/patches/fix-migration-fake-initial-1.patch | 289 +++++++++++++++++++++
debian/patches/fix-migration-fake-initial-2.patch | 297 ++++++++++++++++++++++
debian/patches/series | 2 +
4 files changed, 595 insertions(+)
diff --git a/debian/changelog b/debian/changelog
index c865858..d20ef7a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-django (1:1.10.7-2) UNRELEASED; urgency=medium
+
+ * Accept again migrations depending on initial migrations that
+ can be fake applied. Closes: #863267
+
+ -- Raphaël Hertzog <hertzog at debian.org> Mon, 29 May 2017 16:59:51 +0200
+
python-django (1:1.10.7-1) unstable; urgency=medium
* New upstream security release:
diff --git a/debian/patches/fix-migration-fake-initial-1.patch b/debian/patches/fix-migration-fake-initial-1.patch
new file mode 100644
index 0000000..421bdd3
--- /dev/null
+++ b/debian/patches/fix-migration-fake-initial-1.patch
@@ -0,0 +1,289 @@
+From c6d66195d7f816aeb47a77570bdd3836a99d4183 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rapha=C3=ABl=20Hertzog?= <hertzog at debian.org>
+Date: Mon, 29 May 2017 15:44:39 +0200
+Subject: [PATCH 1/2] Move detect_soft_applied() from
+ django.db.migrations.executor to .loader
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+We want to be able to use that method in
+loader.check_consistent_history() to accept an history where the initial
+migration is going to be fake-applied. Since the executor has the
+knowledge of the loader (but not the opposite), it makes sens to move
+the code around.
+
+Signed-off-by: Raphaël Hertzog <hertzog at debian.org>
+Bug: https://code.djangoproject.com/ticket/28250
+---
+ django/db/migrations/executor.py | 83 +--------------------------------------
+ django/db/migrations/loader.py | 81 ++++++++++++++++++++++++++++++++++++++
+ tests/migrations/test_executor.py | 12 +++---
+ 3 files changed, 88 insertions(+), 88 deletions(-)
+
+--- a/django/db/migrations/executor.py
++++ b/django/db/migrations/executor.py
+@@ -1,8 +1,5 @@
+ from __future__ import unicode_literals
+
+-from django.apps.registry import apps as global_apps
+-from django.db import migrations, router
+-
+ from .exceptions import InvalidMigrationPlan
+ from .loader import MigrationLoader
+ from .recorder import MigrationRecorder
+@@ -235,7 +232,7 @@ class MigrationExecutor(object):
+ if not fake:
+ if fake_initial:
+ # Test to see if this is an already-applied initial migration
+- applied, state = self.detect_soft_applied(state, migration)
++ applied, state = self.loader.detect_soft_applied(state, migration)
+ if applied:
+ fake = True
+ if not fake:
+@@ -290,81 +287,3 @@ class MigrationExecutor(object):
+ if all_applied and key not in applied:
+ self.recorder.record_applied(*key)
+
+- def detect_soft_applied(self, project_state, migration):
+- """
+- Tests whether a migration has been implicitly applied - that the
+- tables or columns it would create exist. This is intended only for use
+- on initial migrations (as it only looks for CreateModel and AddField).
+- """
+- def should_skip_detecting_model(migration, model):
+- """
+- No need to detect tables for proxy models, unmanaged models, or
+- models that can't be migrated on the current database.
+- """
+- return (
+- model._meta.proxy or not model._meta.managed or not
+- router.allow_migrate(
+- self.connection.alias, migration.app_label,
+- model_name=model._meta.model_name,
+- )
+- )
+-
+- if migration.initial is None:
+- # Bail if the migration isn't the first one in its app
+- if any(app == migration.app_label for app, name in migration.dependencies):
+- return False, project_state
+- elif migration.initial is False:
+- # Bail if it's NOT an initial migration
+- return False, project_state
+-
+- if project_state is None:
+- after_state = self.loader.project_state((migration.app_label, migration.name), at_end=True)
+- else:
+- after_state = migration.mutate_state(project_state)
+- apps = after_state.apps
+- found_create_model_migration = False
+- found_add_field_migration = False
+- existing_table_names = self.connection.introspection.table_names(self.connection.cursor())
+- # Make sure all create model and add field operations are done
+- for operation in migration.operations:
+- if isinstance(operation, migrations.CreateModel):
+- model = apps.get_model(migration.app_label, operation.name)
+- if model._meta.swapped:
+- # We have to fetch the model to test with from the
+- # main app cache, as it's not a direct dependency.
+- model = global_apps.get_model(model._meta.swapped)
+- if should_skip_detecting_model(migration, model):
+- continue
+- if model._meta.db_table not in existing_table_names:
+- return False, project_state
+- found_create_model_migration = True
+- elif isinstance(operation, migrations.AddField):
+- model = apps.get_model(migration.app_label, operation.model_name)
+- if model._meta.swapped:
+- # We have to fetch the model to test with from the
+- # main app cache, as it's not a direct dependency.
+- model = global_apps.get_model(model._meta.swapped)
+- if should_skip_detecting_model(migration, model):
+- continue
+-
+- table = model._meta.db_table
+- field = model._meta.get_field(operation.name)
+-
+- # Handle implicit many-to-many tables created by AddField.
+- if field.many_to_many:
+- if field.remote_field.through._meta.db_table not in existing_table_names:
+- return False, project_state
+- else:
+- found_add_field_migration = True
+- continue
+-
+- column_names = [
+- column.name for column in
+- self.connection.introspection.get_table_description(self.connection.cursor(), table)
+- ]
+- if field.column not in column_names:
+- return False, project_state
+- found_add_field_migration = True
+- # If we get this far and we found at least one CreateModel or AddField migration,
+- # the migration is considered implicitly applied.
+- return (found_create_model_migration or found_add_field_migration), after_state
+--- a/django/db/migrations/loader.py
++++ b/django/db/migrations/loader.py
+@@ -4,8 +4,9 @@ import os
+ import sys
+ from importlib import import_module
+
+-from django.apps import apps
++from django.apps import apps as global_apps
+ from django.conf import settings
++from django.db import migrations, router
+ from django.db.migrations.graph import MigrationGraph
+ from django.db.migrations.recorder import MigrationRecorder
+ from django.utils import six
+@@ -56,7 +57,7 @@ class MigrationLoader(object):
+ if app_label in settings.MIGRATION_MODULES:
+ return settings.MIGRATION_MODULES[app_label]
+ else:
+- app_package_name = apps.get_app_config(app_label).name
++ app_package_name = global_apps.get_app_config(app_label).name
+ return '%s.%s' % (app_package_name, MIGRATIONS_MODULE_NAME)
+
+ def load_disk(self):
+@@ -66,7 +67,7 @@ class MigrationLoader(object):
+ self.disk_migrations = {}
+ self.unmigrated_apps = set()
+ self.migrated_apps = set()
+- for app_config in apps.get_app_configs():
++ for app_config in global_apps.get_app_configs():
+ # Get the migrations module directory
+ module_name = self.migrations_module(app_config.label)
+ if module_name is None:
+@@ -315,3 +316,82 @@ class MigrationLoader(object):
+ See graph.make_state for the meaning of "nodes" and "at_end"
+ """
+ return self.graph.make_state(nodes=nodes, at_end=at_end, real_apps=list(self.unmigrated_apps))
++
++ def detect_soft_applied(self, project_state, migration):
++ """
++ Tests whether a migration has been implicitly applied - that the
++ tables or columns it would create exist. This is intended only for use
++ on initial migrations (as it only looks for CreateModel and AddField).
++ """
++ def should_skip_detecting_model(migration, model):
++ """
++ No need to detect tables for proxy models, unmanaged models, or
++ models that can't be migrated on the current database.
++ """
++ return (
++ model._meta.proxy or not model._meta.managed or not
++ router.allow_migrate(
++ self.connection.alias, migration.app_label,
++ model_name=model._meta.model_name,
++ )
++ )
++
++ if migration.initial is None:
++ # Bail if the migration isn't the first one in its app
++ if any(app == migration.app_label for app, name in migration.dependencies):
++ return False, project_state
++ elif migration.initial is False:
++ # Bail if it's NOT an initial migration
++ return False, project_state
++
++ if project_state is None:
++ after_state = self.project_state((migration.app_label, migration.name), at_end=True)
++ else:
++ after_state = migration.mutate_state(project_state)
++ apps = after_state.apps
++ found_create_model_migration = False
++ found_add_field_migration = False
++ existing_table_names = self.connection.introspection.table_names(self.connection.cursor())
++ # Make sure all create model and add field operations are done
++ for operation in migration.operations:
++ if isinstance(operation, migrations.CreateModel):
++ model = apps.get_model(migration.app_label, operation.name)
++ if model._meta.swapped:
++ # We have to fetch the model to test with from the
++ # main app cache, as it's not a direct dependency.
++ model = global_apps.get_model(model._meta.swapped)
++ if should_skip_detecting_model(migration, model):
++ continue
++ if model._meta.db_table not in existing_table_names:
++ return False, project_state
++ found_create_model_migration = True
++ elif isinstance(operation, migrations.AddField):
++ model = apps.get_model(migration.app_label, operation.model_name)
++ if model._meta.swapped:
++ # We have to fetch the model to test with from the
++ # main app cache, as it's not a direct dependency.
++ model = global_apps.get_model(model._meta.swapped)
++ if should_skip_detecting_model(migration, model):
++ continue
++
++ table = model._meta.db_table
++ field = model._meta.get_field(operation.name)
++
++ # Handle implicit many-to-many tables created by AddField.
++ if field.many_to_many:
++ if field.remote_field.through._meta.db_table not in existing_table_names:
++ return False, project_state
++ else:
++ found_add_field_migration = True
++ continue
++
++ column_names = [
++ column.name for column in
++ self.connection.introspection.get_table_description(self.connection.cursor(), table)
++ ]
++ if field.column not in column_names:
++ return False, project_state
++ found_add_field_migration = True
++ # If we get this far and we found at least one CreateModel or AddField migration,
++ # the migration is considered implicitly applied.
++ return (found_create_model_migration or found_add_field_migration), after_state
+--- a/tests/migrations/test_executor.py
++++ b/tests/migrations/test_executor.py
+@@ -326,7 +326,7 @@ class ExecutorTests(MigrationTestBase):
+ global_apps.get_app_config("migrations").models["author"] = migrations_apps.get_model("migrations", "author")
+ try:
+ migration = executor.loader.get_migration("auth", "0001_initial")
+- self.assertIs(executor.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
+ finally:
+ connection.introspection.table_names = old_table_names
+ del global_apps.get_app_config("migrations").models["author"]
+@@ -343,7 +343,7 @@ class ExecutorTests(MigrationTestBase):
+ )
+ def test_detect_soft_applied_add_field_manytomanyfield(self):
+ """
+- executor.detect_soft_applied() detects ManyToManyField tables from an
++ loader.detect_soft_applied() detects ManyToManyField tables from an
+ AddField operation. This checks the case of AddField in a migration
+ with other operations (0001) and the case of AddField in its own
+ migration (0002).
+@@ -365,9 +365,9 @@ class ExecutorTests(MigrationTestBase):
+ self.assertTableExists(table)
+ # Table detection sees 0001 is applied but not 0002.
+ migration = executor.loader.get_migration("migrations", "0001_initial")
+- self.assertIs(executor.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
+ migration = executor.loader.get_migration("migrations", "0002_initial")
+- self.assertIs(executor.detect_soft_applied(None, migration)[0], False)
++ self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], False)
+
+ # Create the tables for both migrations but make it look like neither
+ # has been applied.
+@@ -378,7 +378,7 @@ class ExecutorTests(MigrationTestBase):
+ executor.migrate([("migrations", None)], fake=True)
+ # Table detection sees 0002 is applied.
+ migration = executor.loader.get_migration("migrations", "0002_initial")
+- self.assertIs(executor.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
+
+ # Leave the tables for 0001 except the many-to-many table. That missing
+ # table should cause detect_soft_applied() to return False.
+@@ -386,7 +386,7 @@ class ExecutorTests(MigrationTestBase):
+ for table in tables[2:]:
+ editor.execute(editor.sql_delete_table % {"table": table})
+ migration = executor.loader.get_migration("migrations", "0001_initial")
+- self.assertIs(executor.detect_soft_applied(None, migration)[0], False)
++ self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], False)
+
+ # Cleanup by removing the remaining tables.
+ with connection.schema_editor() as editor:
diff --git a/debian/patches/fix-migration-fake-initial-2.patch b/debian/patches/fix-migration-fake-initial-2.patch
new file mode 100644
index 0000000..bdbd015
--- /dev/null
+++ b/debian/patches/fix-migration-fake-initial-2.patch
@@ -0,0 +1,297 @@
+From e4ed4431d5730e4a8efc3d76d79ee5c16b5a2340 Mon Sep 17 00:00:00 2001
+From: Marten Kenbeek <marten.knbk at gmail.com>
+Date: Wed, 31 May 2017 13:35:43 +0200
+Subject: [PATCH] Fixed #25850 -- Ignored soft applied migrations in
+ consistency check.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Ignored initial migrations that have been soft-applied and may be faked
+with the --fake-initial flag in the migration history consistency
+check. Does not ignore the initial migration if a later migration in
+the same app has been recorded as applied.
+
+Included soft-applied migrations in the pre-migrate project state if
+any of its children has been applied.
+
+Thanks to Raphaël Hertzog for the initial patch.
+
+Bug: https://code.djangoproject.com/ticket/28250
+---
+ django/core/management/commands/makemigrations.py | 2 +-
+ django/core/management/commands/migrate.py | 2 +-
+ django/db/migrations/executor.py | 7 ++-
+ django/db/migrations/loader.py | 28 +++++++---
+ tests/migrations/test_executor.py | 63 +++++++++++++++++++++--
+ tests/migrations/test_loader.py | 27 ++++++++++
+ 6 files changed, 114 insertions(+), 15 deletions(-)
+
+--- a/django/core/management/commands/makemigrations.py
++++ b/django/core/management/commands/makemigrations.py
+@@ -106,7 +106,7 @@ class Command(BaseCommand):
+ for app_label in consistency_check_labels
+ for model in apps.get_app_config(app_label).get_models()
+ )):
+- loader.check_consistent_history(connection)
++ loader.check_consistent_history(connection, fake_initial=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
+--- a/django/core/management/commands/migrate.py
++++ b/django/core/management/commands/migrate.py
+@@ -83,7 +83,7 @@ class Command(BaseCommand):
+ executor = MigrationExecutor(connection, self.migration_progress_callback)
+
+ # Raise an error if any migrations are applied before their dependencies.
+- executor.loader.check_consistent_history(connection)
++ executor.loader.check_consistent_history(connection, fake_initial=options['fake_initial'])
+
+ # Before anything else, see if there's conflicting apps and drop out
+ # hard if there are any
+--- a/django/db/migrations/executor.py
++++ b/django/db/migrations/executor.py
+@@ -76,6 +76,11 @@ class MigrationExecutor(object):
+ for migration, _ in full_plan:
+ if migration in applied_migrations:
+ migration.mutate_state(state, preserve=False)
++ elif any(
++ self.loader.graph.nodes[node] in applied_migrations
++ for node in self.loader.graph.node_map[migration.app_label, migration.name].descendants()
++ ):
++ _, state = self.loader.detect_soft_applied(self.connection, state, migration)
+ return state
+
+ def migrate(self, targets, plan=None, state=None, fake=False, fake_initial=False):
+@@ -232,7 +237,7 @@ class MigrationExecutor(object):
+ if not fake:
+ if fake_initial:
+ # Test to see if this is an already-applied initial migration
+- applied, state = self.loader.detect_soft_applied(state, migration)
++ applied, state = self.loader.detect_soft_applied(self.connection, state, migration)
+ if applied:
+ fake = True
+ if not fake:
+--- a/django/db/migrations/loader.py
++++ b/django/db/migrations/loader.py
+@@ -268,13 +268,14 @@ class MigrationLoader(object):
+ six.reraise(NodeNotFoundError, exc_value, sys.exc_info()[2])
+ raise exc
+
+- def check_consistent_history(self, connection):
++ def check_consistent_history(self, connection, fake_initial=False):
+ """
+ Raise InconsistentMigrationHistory if any applied migrations have
+ unapplied dependencies.
+ """
+ recorder = MigrationRecorder(connection)
+ applied = recorder.applied_migrations()
++ msg = "Migration {}.{} is applied before its dependency {}.{} on database '{}'."
+ for migration in applied:
+ # If the migration is unknown, skip it.
+ if migration not in self.graph.nodes:
+@@ -286,9 +287,22 @@ class MigrationLoader(object):
+ if parent in self.replacements:
+ if all(m in applied for m in self.replacements[parent].replaces):
+ continue
++ # Skip initial migration that is going to be fake-applied
++ # unless a later migration in the same app has been
++ # applied.
++ if migration[0] != parent[0]:
++ if self.detect_soft_applied(connection, None, self.graph.nodes[parent])[0]:
++ if fake_initial:
++ continue
++ else:
++ raise InconsistentMigrationHistory(
++ (msg + " The migration {}.{} may be faked using '--fake-initial'.").format(
++ migration[0], migration[1], parent[0], parent[1],
++ connection.alias, parent[0], parent[1],
++ )
++ )
+ raise InconsistentMigrationHistory(
+- "Migration {}.{} is applied before its dependency "
+- "{}.{} on database '{}'.".format(
++ msg.format(
+ migration[0], migration[1], parent[0], parent[1],
+ connection.alias,
+ )
+@@ -317,7 +331,7 @@ class MigrationLoader(object):
+ """
+ return self.graph.make_state(nodes=nodes, at_end=at_end, real_apps=list(self.unmigrated_apps))
+
+- def detect_soft_applied(self, project_state, migration):
++ def detect_soft_applied(self, connection, project_state, migration):
+ """
+ Tests whether a migration has been implicitly applied - that the
+ tables or columns it would create exist. This is intended only for use
+@@ -331,7 +345,7 @@ class MigrationLoader(object):
+ return (
+ model._meta.proxy or not model._meta.managed or not
+ router.allow_migrate(
+- self.connection.alias, migration.app_label,
++ connection.alias, migration.app_label,
+ model_name=model._meta.model_name,
+ )
+ )
+@@ -351,7 +365,7 @@ class MigrationLoader(object):
+ apps = after_state.apps
+ found_create_model_migration = False
+ found_add_field_migration = False
+- existing_table_names = self.connection.introspection.table_names(self.connection.cursor())
++ existing_table_names = connection.introspection.table_names(connection.cursor())
+ # Make sure all create model and add field operations are done
+ for operation in migration.operations:
+ if isinstance(operation, migrations.CreateModel):
+@@ -387,7 +401,7 @@ class MigrationLoader(object):
+
+ column_names = [
+ column.name for column in
+- self.connection.introspection.get_table_description(self.connection.cursor(), table)
++ connection.introspection.get_table_description(connection.cursor(), table)
+ ]
+ if field.column not in column_names:
+ return False, project_state
+--- a/tests/migrations/test_executor.py
++++ b/tests/migrations/test_executor.py
+@@ -297,6 +297,59 @@ class ExecutorTests(MigrationTestBase):
+ self.assertTableNotExists("migrations_author")
+ self.assertTableNotExists("migrations_tribble")
+
++ @override_settings(MIGRATION_MODULES={
++ "migrations": "migrations.test_migrations_first",
++ "migrations2": "migrations2.test_migrations_2_first",
++ })
++ def test_create_project_state_soft_applied(self):
++ """
++ _create_project_state(with_applied_migrations=True) should apply
++ soft-applied migrations to the project state.
++ """
++ executor = MigrationExecutor(connection)
++ # Were the tables there before?
++ self.assertTableNotExists("migrations_author")
++ self.assertTableNotExists("migrations_tribble")
++ # Run it normally
++ self.assertEqual(
++ executor.migration_plan([("migrations2", "0002_second")]),
++ [
++ (executor.loader.graph.nodes["migrations", "thefirst"], False),
++ (executor.loader.graph.nodes["migrations2", "0001_initial"], False),
++ (executor.loader.graph.nodes["migrations2", "0002_second"], False),
++ ],
++ )
++ executor.migrate([("migrations2", "0002_second")])
++ # Are the tables there now?
++ self.assertTableExists("migrations_author")
++ self.assertTableExists("migrations_tribble")
++ # Fake-revert the initial migration in "migrations". We can't
++ # fake-migrate backwards as that would revert other migrations
++ # as well.
++ recorder = MigrationRecorder(connection)
++ recorder.record_unapplied("migrations", "thefirst")
++ # Check if models and fields in soft-applied migrations are
++ # in the project state.
++ executor.loader.build_graph()
++ project_state = executor._create_project_state(with_applied_migrations=True)
++ self.assertIn(("migrations", "author"), project_state.models)
++ self.assertIn(("migrations", "tribble"), project_state.models)
++ # Apply migration with --fake-initial
++ executor.loader.build_graph()
++ self.assertEqual(
++ executor.migration_plan([("migrations", "thefirst")]),
++ [
++ (executor.loader.graph.nodes["migrations", "thefirst"], False),
++ ],
++ )
++ executor.migrate([("migrations", "thefirst")], fake_initial=True)
++ # And migrate back to clean up the database
++ executor.loader.build_graph()
++ executor.migrate([("migrations", None)])
++ self.assertTableNotExists("migrations_author")
++ self.assertTableNotExists("migrations_tribble")
++ executor.loader.build_graph()
++
+ @override_settings(
+ MIGRATION_MODULES={
+ "migrations": "migrations.test_migrations_custom_user",
+@@ -326,7 +379,7 @@ class ExecutorTests(MigrationTestBase):
+ global_apps.get_app_config("migrations").models["author"] = migrations_apps.get_model("migrations", "author")
+ try:
+ migration = executor.loader.get_migration("auth", "0001_initial")
+- self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(connection, None, migration)[0], True)
+ finally:
+ connection.introspection.table_names = old_table_names
+ del global_apps.get_app_config("migrations").models["author"]
+@@ -365,9 +418,9 @@ class ExecutorTests(MigrationTestBase):
+ self.assertTableExists(table)
+ # Table detection sees 0001 is applied but not 0002.
+ migration = executor.loader.get_migration("migrations", "0001_initial")
+- self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(connection, None, migration)[0], True)
+ migration = executor.loader.get_migration("migrations", "0002_initial")
+- self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], False)
++ self.assertIs(executor.loader.detect_soft_applied(connection, None, migration)[0], False)
+
+ # Create the tables for both migrations but make it look like neither
+ # has been applied.
+@@ -378,7 +431,7 @@ class ExecutorTests(MigrationTestBase):
+ executor.migrate([("migrations", None)], fake=True)
+ # Table detection sees 0002 is applied.
+ migration = executor.loader.get_migration("migrations", "0002_initial")
+- self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], True)
++ self.assertIs(executor.loader.detect_soft_applied(connection, None, migration)[0], True)
+
+ # Leave the tables for 0001 except the many-to-many table. That missing
+ # table should cause detect_soft_applied() to return False.
+@@ -386,7 +439,7 @@ class ExecutorTests(MigrationTestBase):
+ for table in tables[2:]:
+ editor.execute(editor.sql_delete_table % {"table": table})
+ migration = executor.loader.get_migration("migrations", "0001_initial")
+- self.assertIs(executor.loader.detect_soft_applied(None, migration)[0], False)
++ self.assertIs(executor.loader.detect_soft_applied(connection, None, migration)[0], False)
+
+ # Cleanup by removing the remaining tables.
+ with connection.schema_editor() as editor:
+--- a/tests/migrations/test_loader.py
++++ b/tests/migrations/test_loader.py
+@@ -8,7 +8,7 @@ from django.db.migrations.exceptions imp
+ )
+ from django.db.migrations.loader import MigrationLoader
+ from django.db.migrations.recorder import MigrationRecorder
+-from django.test import TestCase, modify_settings, override_settings
++from django.test import TestCase, modify_settings, override_settings, mock
+ from django.utils import six
+
+
+@@ -402,6 +402,31 @@ class LoaderTests(TestCase):
+ loader.check_consistent_history(connection)
+
+ @override_settings(MIGRATION_MODULES={
++ "migrations": "migrations.test_migrations_first",
++ "migrations2": "migrations2.test_migrations_2_first",
++ })
++ @modify_settings(INSTALLED_APPS={'append': 'migrations2'})
++ @mock.patch.object(MigrationLoader, 'detect_soft_applied', return_value=(True, None))
++ def test_check_consistent_history_fake_initial(self, mock_detect_soft_applied):
++ """
++ MigrationLoader.check_consistent_history() should ignore soft-applied
++ initial migrations unless a later migration in the same app has been
++ applied.
++ """
++ loader = MigrationLoader(connection=None)
++ recorder = MigrationRecorder(connection)
++ recorder.record_applied('migrations2', '0001_initial')
++ recorder.record_applied('migrations2', '0002_second')
++ loader.check_consistent_history(connection, fake_initial=True)
++ recorder.record_applied('migrations', 'second')
++ msg = (
++ "Migration migrations.second is applied before its dependency "
++ "migrations.thefirst on database 'default'."
++ )
++ with self.assertRaisesMessage(InconsistentMigrationHistory, msg):
++ loader.check_consistent_history(connection, fake_initial=True)
++
++ @override_settings(MIGRATION_MODULES={
+ "app1": "migrations.test_migrations_squashed_ref_squashed.app1",
+ "app2": "migrations.test_migrations_squashed_ref_squashed.app2",
+ })
diff --git a/debian/patches/series b/debian/patches/series
index 9b1ddfc..7786c21 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1,4 @@
02_disable-sources-in-sphinxdoc.diff
06_use_debian_geoip_database_as_default.diff
+fix-migration-fake-initial-1.patch
+fix-migration-fake-initial-2.patch
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django.git
More information about the Python-modules-commits
mailing list