示例#1
0
    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, "testapp.Writer")
示例#2
0
 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")
示例#3
0
 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)
         all_items_equal = lambda seq: 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],
             })
             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)
示例#4
0
 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)
示例#5
0
    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)
示例#6
0
 def test_rename_field(self):
     "Tests autodetection of renamed fields"
     # Make state
     before = self.make_project_state([self.author_name])
     after = self.make_project_state([self.author_name_renamed])
     autodetector = MigrationAutodetector(before, after, MigrationQuestioner({"ask_rename": True}))
     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__, "RenameField")
     self.assertEqual(action.old_name, "name")
     self.assertEqual(action.new_name, "names")
示例#7
0
    def test__migrations(self):
        app_labels = {
            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 = {
            (app_label, conflict)
            for app_label, conflict in loader.detect_conflicts().items()
            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:
            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)
示例#8
0
    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.assertNumberMigrations(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")
        # Now that RenameModel handles related fields too, there should be
        # no AlterField for the related field.
        self.assertNumberMigrations(changes, 'otherapp', 0)
示例#9
0
    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" % 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, self.include_header)

                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))
                    self.stdout.write(writer.as_string())
示例#10
0
 def test_ask_initial_with_disabled_migrations(self):
     questioner = MigrationQuestioner()
     self.assertIs(False, questioner.ask_initial('migrations'))
示例#11
0
 def __init__(self, from_state, to_state, questioner=None):
     self.from_state = from_state
     self.to_state = to_state
     self.questioner = questioner or MigrationQuestioner()
示例#12
0
 def test_ask_not_null_alteration(self):
     questioner = MigrationQuestioner()
     self.assertIsNone(
         questioner.ask_not_null_alteration('field_name', 'model_name'))
示例#13
0
 def test_ask_initial_with_disabled_migrations(self):
     questioner = MigrationQuestioner()
     self.assertIs(False, questioner.ask_initial('migrations'))
示例#14
0
    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)

                if not self.dry_run:
                    # Write the merge migrations file to the disk
                    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)
                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())
示例#15
0
 def __init__(self, from_state, to_state, questioner=None):
     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}
示例#16
0
    def handle_merge(self, loader, conflicts):
        """
        Handles merging together conflicted changes 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, change_names in conflicts.items():
            # Grab out the changes in question, and work out their
            # common ancestor.
            merge_changes = []
            for change_name in change_names:
                change = loader.get_change(app_label, change_name)
                change.ancestry = [
                    mig for mig in loader.graph.forwards_plan((app_label, change_name))
                    if mig[0] == change.app_label
                ]
                merge_changes.append(change)

            def all_items_equal(seq):
                return all(item == seq[0] for item in seq[1:])

            merge_changes_generations = zip(*[m.ancestry for m in merge_changes])
            common_ancestor_count = sum(1 for common_ancestor_generation
                                        in takewhile(all_items_equal, merge_changes_generations))
            if not common_ancestor_count:
                raise ValueError('Could not find common ancestor of %s' % change_names)
            # Now work out the operations along each divergent branch
            for change in merge_changes:
                change.branch = change.ancestry[common_ancestor_count:]
                changes_ops = (loader.get_change(node_app, node_name).operations
                               for node_app, node_name in change.branch)
                change.merged_operations = sum(changes_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 change in merge_changes:
                    self.stdout.write(self.style.MIGRATE_LABEL('  Branch %s' % change.name))
                    for operation in change.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 changes needing merging.
                numbers = [
                    ChangeAutodetector.parse_number(change.name)
                    for change in merge_changes
                ]
                try:
                    biggest_number = max(x for x in numbers if x is not None)
                except ValueError:
                    biggest_number = 1
                subclass = type('Change', (Change, ), {
                    'dependencies': [(app_label, change.name) for change in merge_changes],
                })
                change_name = '%04i_%s' % (
                    biggest_number + 1,
                    self.change_name or ('merge_%s' % get_migration_name_timestamp())
                )
                new_change = subclass(change_name, app_label)
                writer = ChangeWriter(new_change)

                if not self.dry_run:
                    # Write the merge changes file to the disk
                    with io.open(writer.path, 'w', encoding='utf-8') as fh:
                        fh.write(writer.as_string())
                    if self.verbosity > 0:
                        self.stdout.write('\nCreated new merge change %s' % writer.path)
                elif self.verbosity == 3:
                    # Alternatively, makechanges --merge --dry-run --verbosity 3
                    # will output the merge changes to stdout rather than saving
                    # the file to the disk.
                    self.stdout.write(self.style.MIGRATE_HEADING(
                        "Full merge changes file '%s':" % writer.filename) + '\n'
                    )
                    self.stdout.write('%s\n' % writer.as_string())