Example #1
0
def find_unsafe_migrations(connection):
    loader = MigrationLoader(connection)

    disk_migrations = set(loader.disk_migrations.keys())
    new_migrations = disk_migrations.difference(loader.applied_migrations)

    unsafe_migrations = []
    for app_name, migration_name in new_migrations:
        migration = loader.get_migration(app_name, migration_name)
        project_state = loader.project_state((app_name, migration_name),
                                             at_end=False)

        result = analyze_migration(connection, migration, project_state)
        if result:
            unsafe_migrations.append(result)

    unsafe_migrations = sorted(unsafe_migrations,
                               key=operator.attrgetter("app_name",
                                                       "migration_name"))

    conflicts = loader.detect_conflicts()

    for app, names in conflicts.items():
        unsafe_migrations.append(
            MigrationConflict(app_name=app, migration_names=names))

    return unsafe_migrations
Example #2
0
class ResourceMigrationLoader(object):
    def __init__(self):
        self._loader = MigrationLoader(None, ignore_no_migrations=True)

    def is_first_make(self):
        migration_app_labels = {
            migration_tuple[0]
            for migration_tuple in self._loader.disk_migrations
        }
        return APP_LABEL not in migration_app_labels

    def last_migration(self):
        leaf_migrations = self._loader.graph.leaf_nodes(APP_LABEL)

        if not leaf_migrations:
            return None

        if len(leaf_migrations) > 1:
            raise LookupError('Found multiple leaf migrations: %s' %
                              leaf_migrations)

        last_migration_info = leaf_migrations[0]

        return self._loader.get_migration(*last_migration_info)
    def handle(self, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        app_label = options['app_label']
        start_migration_name = options['start_migration_name']
        migration_name = options['migration_name']
        no_optimize = options['no_optimize']
        squashed_name = options['squashed_name']
        include_header = options['include_header']
        # Validate app_label.
        try:
            apps.get_app_config(app_label)
        except LookupError as err:
            raise CommandError(str(err))
        # Load the current graph state, check the BLOG and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label)

        migration = self.find_migration(loader, app_label, migration_name)

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label,
                                                      migration.name))
            if al == migration.app_label
        ]

        if start_migration_name:
            start_migration = self.find_migration(loader, app_label,
                                                  start_migration_name)
            start = loader.get_migration(start_migration.app_label,
                                         start_migration.name)
            try:
                start_index = migrations_to_squash.index(start)
                migrations_to_squash = migrations_to_squash[start_index:]
            except ValueError:
                raise CommandError(
                    "The migration '%s' cannot be found. Maybe it comes after "
                    "the migration '%s'?\n"
                    "Have a look at:\n"
                    "  python manage.py showmigrations %s\n"
                    "to debug this issue." %
                    (start_migration, migration, app_label))

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        # We need to take all dependencies from the first migration in the list
        # as it may be 0002 depending on 0001
        first_migration = True
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations"
                    % get_docs_version())
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label or first_migration:
                    dependencies.add(dependency)
            first_migration = False

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations,
                                                migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type(
            "Migration", (migrations.Migration, ), {
                "dependencies": dependencies,
                "operations": new_operations,
                "replaces": replaces,
            })
        if start_migration_name:
            if squashed_name:
                # Use the name from --squashed-name.
                prefix, _ = start_migration.name.split('_', 1)
                name = '%s_%s' % (prefix, squashed_name)
            else:
                # Generate a name.
                name = '%s_squashed_%s' % (start_migration.name,
                                           migration.name)
            new_migration = subclass(name, app_label)
        else:
            name = '0001_%s' % (squashed_name
                                or 'squashed_%s' % migration.name)
            new_migration = subclass(name, app_label)
            new_migration.initial = True

        # Write out the new migration file
        writer = MigrationWriter(new_migration, include_header)
        with open(writer.path, "w", encoding='utf-8') as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    'Created new squashed migration %s' % writer.path) + '\n'
                '  You should commit this migration but leave the old ones in place;\n'
                '  the new migration will be used for new installs. Once you are sure\n'
                '  all instances of the codebase have applied the migrations you squashed,\n'
                '  you can delete them.')
            if writer.needs_manual_porting:
                self.stdout.write(
                    self.style.MIGRATE_HEADING('Manual porting required') +
                    '\n'
                    '  Your migrations contained functions that must be manually copied over,\n'
                    '  as we could not safely copy their implementation.\n'
                    '  See the comment at the top of the squashed migration for details.'
                )
    def handle(self, **options):
        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        self.dry_run = options['dry_run']
        app_label = options['app_label']
        squashed_migration_name = options['squashed_migration_name']
        database = options['database']

        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[database])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so delete_squashed_migrations on "
                "it makes no sense)" % app_label
            )

        squashed_migration = None
        if squashed_migration_name:
            squashed_migration = self.find_migration(loader, app_label, squashed_migration_name)
            if not squashed_migration.replaces:
                raise CommandError(
                    "The migration %s %s is not a squashed migration." %
                    (squashed_migration.app_label, squashed_migration.name)
                )
        else:
            leaf_nodes = loader.graph.leaf_nodes(app=app_label)
            migration = loader.get_migration(*leaf_nodes[0])
            previous_migrations = [
                loader.get_migration(al, mn)
                for al, mn in loader.graph.forwards_plan((migration.app_label, migration.name))
                if al == migration.app_label
            ]
            migrations = previous_migrations + [migration]
            for migration in migrations:
                if migration.replaces:
                    squashed_migration = migration
                    break

            if not squashed_migration:
                raise CommandError(
                    "Cannot find a squashed migration in app '%s'." %
                    (app_label)
                )

        files_to_delete = []
        for al, mn in squashed_migration.replaces:
            try:
                migration = loader.disk_migrations[al, mn]
            except KeyError:
                if self.verbosity > 0:
                    self.stderr.write("Couldn't find migration file for %s %s\n"
                                      % (al, mn))
            else:
                pyc_file = inspect.getfile(migration.__class__)
                files_to_delete.append(pyc_file)
                if pyc_file.endswith(PYC):
                    py_file = py_from_pyc(pyc_file)
                    files_to_delete.append(py_file)

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will delete the following files:"))
            for fn in files_to_delete:
                self.stdout.write(" - %s" % fn)

            if not self.confirm():
                return

        for fn in files_to_delete:
            try:
                if not self.dry_run:
                    os.remove(fn)
            except OSError:
                if self.verbosity > 0:
                    self.stderr.write("Couldn't delete %s\n" % (fn,))

        # Try and delete replaces only if it's all on one line
        squashed_migration_fn = inspect.getfile(squashed_migration.__class__)
        if squashed_migration_fn.endswith(PYC):
            squashed_migration_fn = py_from_pyc(squashed_migration_fn)
        with open(squashed_migration_fn) as fp:
            squashed_migration_lines = list(fp)

        delete_lines = []
        for i, line in enumerate(squashed_migration_lines):
            if REPLACES_REGEX.match(line):
                delete_lines.append(i)
                if i > 0 and squashed_migration_lines[i - 1].strip() == '':
                    delete_lines.insert(0, i - 1)
                break
        if not delete_lines:
            raise CommandError(
                ("Couldn't find 'replaces =' line in file %s. "
                 "Please finish cleaning up manually.") % (squashed_migration_fn,)
            )

        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING(
                "Will delete line %s%s from file %s" %
                (delete_lines[0],
                 ' and ' + str(delete_lines[1]) if len(delete_lines) > 1 else "",
                 squashed_migration_fn)))

            if not self.confirm():
                return

        for line_num in sorted(delete_lines, reverse=True):
            del squashed_migration_lines[line_num]

        with open(squashed_migration_fn, 'w') as fp:
            if not self.dry_run:
                fp.write("".join(squashed_migration_lines))
Example #5
0
    def handle(self, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        app_label = options['app_label']
        start_migration_name = options['start_migration_name']
        migration_name = options['migration_name']
        no_optimize = options['no_optimize']

        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label
            )

        migration = self.find_migration(loader, app_label, migration_name)

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label, migration.name))
            if al == migration.app_label
        ]

        if start_migration_name:
            start_migration = self.find_migration(loader, app_label, start_migration_name)
            start = loader.get_migration(start_migration.app_label, start_migration.name)
            try:
                start_index = migrations_to_squash.index(start)
                migrations_to_squash = migrations_to_squash[start_index:]
            except ValueError:
                raise CommandError(
                    "The migration '%s' cannot be found. Maybe it comes after "
                    "the migration '%s'?\n"
                    "Have a look at:\n"
                    "  python manage.py showmigrations %s\n"
                    "to debug this issue." % (start_migration, migration, app_label)
                )

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        # We need to take all dependencies from the first migration in the list
        # as it may be 0002 depending on 0001
        first_migration = True
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations" % get_docs_version()
                )
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label or first_migration:
                    dependencies.add(dependency)
            first_migration = False

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations, migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations))
                    )

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type("Migration", (migrations.Migration, ), {
            "dependencies": dependencies,
            "operations": new_operations,
            "replaces": replaces,
        })
        if start_migration_name:
            new_migration = subclass("%s_squashed_%s" % (start_migration.name, migration.name), app_label)
        else:
            new_migration = subclass("0001_squashed_%s" % migration.name, app_label)
            new_migration.initial = True

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Created new squashed migration %s" % writer.path))
            self.stdout.write("  You should commit this migration but leave the old ones in place;")
            self.stdout.write("  the new migration will be used for new installs. Once you are sure")
            self.stdout.write("  all instances of the codebase have applied the migrations you squashed,")
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write("  Your migrations contained functions that must be manually copied over,")
                self.stdout.write("  as we could not safely copy their implementation.")
                self.stdout.write("  See the comment at the top of the squashed migration for details.")
Example #6
0
    def handle(self, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')
        app_label = options['app_label']
        migration_name = options['migration_name']
        no_optimize = options['no_optimize']

        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label)
        try:
            migration = 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))

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label,
                                                      migration.name))
            if al == migration.app_label
        ]

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations"
                    % get_docs_version())
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label:
                    dependencies.add(dependency)

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations,
                                                migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type(
            "Migration", (migrations.Migration, ), {
                "dependencies": dependencies,
                "operations": new_operations,
                "replaces": replaces,
                "initial": True,
            })
        new_migration = subclass("0001_squashed_%s" % migration.name,
                                 app_label)

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Created new squashed migration %s" % writer.path))
            self.stdout.write(
                "  You should commit this migration but leave the old ones in place;"
            )
            self.stdout.write(
                "  the new migration will be used for new installs. Once you are sure"
            )
            self.stdout.write(
                "  all instances of the codebase have applied the migrations you squashed,"
            )
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write(
                    "  Your migrations contained functions that must be manually copied over,"
                )
                self.stdout.write(
                    "  as we could not safely copy their implementation.")
                self.stdout.write(
                    "  See the comment at the top of the squashed migration for details."
                )
    def handle(self, **options):
        self.verbosity = options["verbosity"]
        self.interactive = options["interactive"]
        self.dry_run = options["dry_run"]
        app_label = options["app_label"]
        squashed_migration_name = options["squashed_migration_name"]

        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so delete_squashed_migrations on " "it makes no sense)" % app_label
            )

        squashed_migration = None
        if squashed_migration_name:
            squashed_migration = self.find_migration(loader, app_label, squashed_migration_name)
            if not squashed_migration.replaces:
                raise CommandError(
                    "The migration %s %s is not a squashed migration."
                    % (squashed_migration.app_label, squashed_migration.name)
                )
        else:
            leaf_nodes = loader.graph.leaf_nodes(app=app_label)
            migration = loader.get_migration(*leaf_nodes[0])
            previous_migrations = [
                loader.get_migration(al, mn)
                for al, mn in loader.graph.forwards_plan((migration.app_label, migration.name))
                if al == migration.app_label
            ]
            migrations = previous_migrations + [migration]
            for migration in migrations:
                if migration.replaces:
                    squashed_migration = migration
                    break

            if not squashed_migration:
                raise CommandError("Cannot find a squashed migration in app '%s'." % (app_label))

        files_to_delete = []
        for al, mn in squashed_migration.replaces:
            try:
                migration = loader.disk_migrations[al, mn]
            except KeyError:
                if self.verbosity > 0:
                    self.stderr.write("Couldn't find migration file for %s %s\n" % (al, mn))
            else:
                pyc_file = inspect.getfile(migration.__class__)
                files_to_delete.append(pyc_file)
                if pyc_file.endswith(PYC):
                    py_file = py_from_pyc(pyc_file)
                    files_to_delete.append(py_file)

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will delete the following files:"))
            for fn in files_to_delete:
                self.stdout.write(" - %s" % fn)

            if not self.confirm():
                return

        for fn in files_to_delete:
            try:
                if not self.dry_run:
                    os.remove(fn)
            except OSError:
                if self.verbosity > 0:
                    self.stderr.write("Couldn't delete %s\n" % (fn,))

        # Try and delete replaces only if it's all on one line
        squashed_migration_fn = inspect.getfile(squashed_migration.__class__)
        if squashed_migration_fn.endswith(PYC):
            squashed_migration_fn = py_from_pyc(squashed_migration_fn)
        with open(squashed_migration_fn) as fp:
            squashed_migration_lines = list(fp)

        delete_lines = []
        for i, line in enumerate(squashed_migration_lines):
            if REPLACES_REGEX.match(line):
                delete_lines.append(i)
                if i > 0 and squashed_migration_lines[i - 1].strip() == "":
                    delete_lines.insert(0, i - 1)
                break
        if not delete_lines:
            raise CommandError(
                ("Couldn't find 'replaces =' line in file %s. " "Please finish cleaning up manually.")
                % (squashed_migration_fn,)
            )

        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will delete line %s%s from file %s"
                    % (
                        delete_lines[0],
                        " and " + str(delete_lines[1]) if len(delete_lines) > 1 else "",
                        squashed_migration_fn,
                    )
                )
            )

            if not self.confirm():
                return

        for line_num in sorted(delete_lines, reverse=True):
            del squashed_migration_lines[line_num]

        with open(squashed_migration_fn, "w") as fp:
            if not self.dry_run:
                fp.write("".join(squashed_migration_lines))