Commit db97a884 authored by Andrei Kulakov's avatar Andrei Kulakov Committed by Tim Graham
Browse files

Fixed #24375 -- Added Migration.initial attribute

The new attribute is checked when the `migrate --fake-initial` option
is used. initial will be set to True for all initial migrations (this
is particularly useful when initial migrations are split) as well as
for squashed migrations.
parent a2b999df
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ answer newbie questions, and generally made Django that much better:
    Ana Krivokapic <https://github.com/infraredgirl>
    Andi Albrecht <albrecht.andi@gmail.com>
    André Ericson <de.ericson@gmail.com>
    Andrei Kulakov <andrei.avk@gmail.com>
    Andreas
    Andreas Mock <andreas.mock@web.de>
    Andreas Pelme <andreas@pelme.se>
+1 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ class Command(BaseCommand):
            "dependencies": dependencies,
            "operations": new_operations,
            "replaces": replaces,
            "initial": True,
        })
        new_migration = subclass("0001_squashed_%s" % migration.name, app_label)

+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ class MigrationAutodetector(object):
        self.from_state = from_state
        self.to_state = to_state
        self.questioner = questioner or MigrationQuestioner()
        self.existing_apps = {app for app, model in from_state.models}

    def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
        """
@@ -297,6 +298,7 @@ class MigrationAutodetector(object):
                        instance = subclass("auto_%i" % (len(self.migrations.get(app_label, [])) + 1), app_label)
                        instance.dependencies = list(dependencies)
                        instance.operations = chopped
                        instance.initial = app_label not in self.existing_apps
                        self.migrations.setdefault(app_label, []).append(instance)
                        chop_mode = False
                    else:
+32 −9
Original line number Diff line number Diff line
@@ -197,19 +197,25 @@ class MigrationExecutor(object):
    def detect_soft_applied(self, project_state, migration):
        """
        Tests whether a migration has been implicitly applied - that the
        tables it would create exist. This is intended only for use
        on initial migrations (as it only looks for CreateModel).
        tables or columns it would create exist. This is intended only for use
        on initial migrations (as it only looks for CreateModel and AddField).
        """
        if migration.initial is None:
            # Bail if the migration isn't the first one in its app
        if [name for app, name in migration.dependencies if app == migration.app_label]:
            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_migration = False
        # Make sure all create model are done
        found_create_model_migration = False
        found_add_field_migration = False
        # 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)
@@ -217,9 +223,26 @@ class MigrationExecutor(object):
                    # 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 model._meta.proxy or not model._meta.managed:
                    continue
                if model._meta.db_table not in self.connection.introspection.table_names(self.connection.cursor()):
                    return False, project_state
                found_create_migration = True
        # If we get this far and we found at least one CreateModel migration,
                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 model._meta.proxy or not model._meta.managed:
                    continue

                table = model._meta.db_table
                db_field = model._meta.get_field(operation.name).column
                fields = self.connection.introspection.get_table_description(self.connection.cursor(), table)
                if db_field not in (f.name for f in fields):
                    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_migration, after_state
        return (found_create_model_migration or found_add_field_migration), after_state
+7 −0
Original line number Diff line number Diff line
@@ -41,6 +41,13 @@ class Migration(object):
    # are not applied.
    replaces = []

    # Is this an initial migration? Initial migrations are skipped on
    # --fake-initial if the table or fields already exist. If None, check if
    # the migration has any dependencies to determine if there are dependencies
    # to tell if db introspection needs to be done. If True, always perform
    # introspection. If False, never perform introspection.
    initial = None

    def __init__(self, name, app_label):
        self.name = name
        self.app_label = app_label
Loading