def _get_delete_model_migration(self, dependencies=[]):
        database_operations = []
        state_operations = []

        state_operations.append(migrations.DeleteModel(name=self.model_name))

        migration = Migration('delete_model', self.source_app)
        migration.dependencies = dependencies
        migration.operations = [
            migrations.SeparateDatabaseAndState(
                database_operations=database_operations,
                state_operations=state_operations,
            )
        ]
        return migration
    def test_has_errors(self):
        linter = MigrationLinter()
        self.assertFalse(linter.has_errors)

        m = Migration("0001_create_table", "app_add_not_null_column")
        linter.lint_migration(m)
        self.assertFalse(linter.has_errors)

        m = Migration("0002_add_new_not_null_field", "app_add_not_null_column")
        linter.lint_migration(m)
        self.assertTrue(linter.has_errors)

        m = Migration("0001_create_table", "app_add_not_null_column")
        linter.lint_migration(m)
        self.assertTrue(linter.has_errors)
Exemple #3
0
    def _gather_migrations_git(self, git_commit_id):
        from django.db.migrations.loader import MIGRATIONS_MODULE_NAME

        migrations = []
        # Get changes since specified commit
        git_diff_command = (
            "cd {0} && git diff --relative --name-only --diff-filter=AR {1}"
        ).format(self.django_path, git_commit_id)
        logger.info("Executing {0}".format(git_diff_command))
        diff_process = Popen(git_diff_command, shell=True, stdout=PIPE, stderr=PIPE)
        for line in map(clean_bytes_to_str, diff_process.stdout.readlines()):
            # Only gather lines that include added migrations
            if (
                re.search(r"/{0}/.*\.py".format(MIGRATIONS_MODULE_NAME), line)
                and "__init__" not in line
            ):
                app_label, name = split_migration_path(line)
                migrations.append(Migration(name, app_label))
        diff_process.wait()

        if diff_process.returncode != 0:
            output = []
            for line in map(clean_bytes_to_str, diff_process.stderr.readlines()):
                output.append(line)
            logger.error("Error while git diff command:\n{}".format("".join(output)))
            raise Exception("Error while executing git diff command")
        return migrations
    def handle(self, app_label, **options):

        self.verbosity = options.get('verbosity')
        fixture_name = options.get('fixture_name')
        self.dry_run = False

        # Make sure the app they asked for exists
        try:
            apps.get_app_config(app_label)
        except LookupError:
            self.stderr.write(
                "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                app_label)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None, ignore_no_migrations=True)

        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=[app_label]),
        )

        migration = Migration("custom", app_label)
        migration.operations.append(LoadFixtureMigration(fixture_name))
        migration.dependencies += loader.graph.nodes.keys()

        changes = {app_label: [migration]}
        changes = autodetector.arrange_for_graph(
            changes=changes,
            graph=loader.graph,
        )
        self.write_migration_files(changes)
        return
    def _get_rename_table_migration(self, dependencies=[]):
        database_operations = []
        state_operations = []

        database_operations.append(
            migrations.AlterModelTable(
                self.model_name.lower(),
                '{}_{}'.format(self.dest_app, self.model_name.lower())))

        migration = Migration('rename_table', self.source_app)
        migration.dependencies = dependencies
        migration.operations = [
            migrations.SeparateDatabaseAndState(
                database_operations=database_operations,
                state_operations=state_operations)
        ]
        return migration
    def handle(self, *args, **options):
        ops = []

        for model in apps.get_models(include_auto_created=True):
            if not hasattr(model, 'VersionModel') or model._meta.proxy:
                continue
            ops.extend(self.build_operations(model))
        if options['initial']:
            m = Migration('0003_triggers', 'share')
            m.dependencies = [('share', '0002_create_share_user')]
        else:
            ml = MigrationLoader(connection=connection)
            ml.build_graph()
            last_share_migration = [
                x[1] for x in ml.graph.leaf_nodes() if x[0] == 'share'
            ][0]
            next_number = '{0:04d}'.format(int(last_share_migration[0:4]) + 1)
            m = Migration(
                '{}_update_trigger_migrations_{}'.format(
                    next_number,
                    datetime.datetime.now().strftime("%Y%m%d_%H%M")), 'share')
            m.dependencies = [('share', '0002_create_share_user'),
                              ('share', last_share_migration)]
        m.operations = ops
        self.write_migration(m)
def plan():
    """Fake migrations plan for testing purposes."""
    migrations_plan = [
        Migration('0001_initial', 'app1'),
        Migration('0002_second', 'app1'),
        Migration('0001_initial', 'app2'),
        Migration('0003_third', 'app1'),
        Migration('0004_fourth', 'app1'),
        Migration('0002_second', 'app2'),
        Migration('0005_fifth', 'app1'),
        Migration('0001_initial', 'app3'),
        Migration('0006_sixth', 'app1'),
    ]
    return [(migration, False) for migration in migrations_plan]
    def _get_create_model_migration(self, dependencies=[]):
        database_operations = []
        state_operations = []

        model_state = self.to_state.models[self.dest_app,
                                           self.model_name.lower()]
        model_opts = self.to_state.apps.get_model(self.dest_app,
                                                  self.model_name)._meta
        related_fields = {}
        for field in model_opts.local_fields:
            if field.remote_field:
                if field.remote_field.model:
                    if not field.remote_field.parent_link:
                        related_fields[field.name] = field
                if (getattr(field.remote_field, 'through', None)
                        and not field.remote_field.through._meta.auto_created):
                    related_fields[field.name] = field
        for field in model_opts.local_many_to_many:
            if field.remote_field.model:
                related_fields[field.name] = field
            if getattr(field.remote_field, 'through', None
                       ) and not field.remote_field.through._meta.auto_created:
                related_fields[field.name] = field

        state_operations.append(
            migrations.CreateModel(
                name=model_state.name,
                fields=[
                    d for d in model_state.fields if d[0] not in related_fields
                ],
                options=model_state.options,
                bases=model_state.bases,
                managers=model_state.managers,
            ))

        migration = Migration('create_model', self.dest_app)
        migration.dependencies = dependencies
        migration.operations = [
            migrations.SeparateDatabaseAndState(
                database_operations=database_operations,
                state_operations=state_operations,
            )
        ]
        return migration
    def handle(self, *args, **options):
        ops = []

        for model in apps.get_models(include_auto_created=True):
            if not hasattr(model, 'VersionModel') or model._meta.proxy:
                continue
            ops.extend(self.build_operations(model))
        if options['initial']:
            m = Migration('0003_triggers', 'share')
            m.dependencies = [('share', '0002_create_share_user')]
        else:
            ml = MigrationLoader(connection=connection)
            ml.build_graph()
            last_share_migration = [x[1] for x in ml.graph.leaf_nodes() if x[0] == 'share'][0]
            next_number = '{0:04d}'.format(int(last_share_migration[0:4]) + 1)
            m = Migration('{}_update_trigger_migrations_{}'.format(next_number, datetime.datetime.now().strftime("%Y%m%d_%H%M")), 'share')
            m.dependencies = [('share', '0002_create_share_user'), ('share', last_share_migration)]
        m.operations = ops
        self.write_migration(m)
Exemple #10
0
    def test_exclude_migration_tests(self):
        m = Migration("0002_add_new_not_null_field", "app_add_not_null_column")

        linter = MigrationLinter(exclude_migration_tests=[], database="mysql")
        linter.lint_migration(m)
        self.assertTrue(linter.has_errors)

        linter = MigrationLinter(exclude_migration_tests=["NOT_NULL"],
                                 database="mysql")
        linter.lint_migration(m)
        self.assertFalse(linter.has_errors)
    def _get_model_fk_migrations(self, dependencies=[]):
        _migrations = []

        model_opts = self.to_state.apps.get_model(self.dest_app,
                                                  self.model_name)._meta

        for field in model_opts.get_fields(include_hidden=True):
            if field.is_relation:
                operations = []
                operations.append(
                    migrations.AlterField(
                        model_name=field.related_model._meta.model_name,
                        name=field.remote_field.name,
                        field=field.remote_field,
                    ))
                migration = Migration('alter_model_fk',
                                      field.related_model._meta.app_label)
                migration.dependencies = dependencies
                migration.operations = operations

                _migrations.append(migration)

        return _migrations
    def handle(self, app_label, *models, **options):
        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)

        if not models and '.' in app_label:
            app_label, models = app_label.split('.', 1)
            models = [models]

        try:
            apps.get_app_config(app_label)
        except LookupError:
            self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # We want to basically write an empty migration, but with some
        # extra bits.

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None)

        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=[app_label]),
        )

        changes = autodetector.arrange_for_graph({
            app_label: [Migration("audit_tables", app_label)]
        }, loader.graph)

        migration = changes[app_label][0]
        migration.dependencies.append(
            ('audit', '0001_initial')
        )
        migration.name = 'audit_%s' % ('_'.join(models[:3]))

        for model_name in models:
            model = apps.get_model(app_label, model_name)
            migration.operations.append(postgres.audit.operations.AuditModel(model))

        self.write_migration_files(changes)
Exemple #13
0
    def create_runpython_migration(self,
                                   app_label,
                                   forwards_backwards,
                                   extra_functions,
                                   extra_dependencies=None):

        # Copy source code, so that we can uninstall this helper app
        # and the migrations still work.
        extra_func_code = "\n\n".join(
            inspect.getsource(f) for f in extra_functions)

        loader = MigrationLoader(None, ignore_no_migrations=True)
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            None,
        )

        changes = {app_label: [Migration("custom", app_label)]}
        changes = autodetector.arrange_for_graph(
            changes=changes,
            graph=loader.graph,
        )

        for app_label, app_migrations in changes.items():
            for migration in app_migrations:
                if extra_dependencies is not None:
                    migration.dependencies.extend(extra_dependencies)
                writer = MigrationWriter(migration)

                migration_string = writer.as_string().decode('utf-8')

                # Add support functions:
                migration_string = migration_string.replace(
                    "\nclass Migration", forwards_backwards + "\n\n" +
                    extra_func_code + "\n\n" + "\nclass Migration")

                # Add operations:
                migration_string = migration_string.replace(
                    "operations = [", "operations = [\n"
                    "        migrations.RunPython(forwards, backwards),")
                with open(writer.path, "wb") as fh:
                    fh.write(migration_string.encode('utf-8'))
Exemple #14
0
 def test_backward(self):
     forward = (Migration("0001_initial", "tests"), True)
     backward = (Migration("0001_initial", "tests"), False)
     self.assertNotEqual(hash_plan([forward]), hash_plan([backward]))
 def apply(self, project_state, schema_editor, collect_sql=False):
     from awx.main.signals import disable_activity_stream
     with disable_activity_stream():
         return Migration.apply(self, project_state, schema_editor,
                                collect_sql)
Exemple #16
0
class CacheTestCase(unittest.TestCase):
    def setUp(self):
        self.test_project_path = os.path.dirname(settings.BASE_DIR)

    @mock.patch(
        "django_migration_linter.MigrationLinter._gather_all_migrations",
        return_value=[
            Migration("0001_create_table", "app_add_not_null_column"),
            Migration("0002_add_new_not_null_field",
                      "app_add_not_null_column"),
        ],
    )
    def test_cache_normal(self, *args):
        linter = MigrationLinter(self.test_project_path)
        linter.old_cache.clear()
        linter.old_cache.save()

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(2, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("OK",
                         cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"])
        self.assertEqual("ERR",
                         cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"])
        self.assertListEqual(
            cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"],
            [{
                "err_msg": "RENAMING tables",
                "code": "RENAME_TABLE",
                "table": None,
                "column": None,
            }],
        )

        # Start the Linter again -> should use cache now.
        linter = MigrationLinter(self.test_project_path)

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            analyse_sql_statements_mock.assert_not_called()

        self.assertTrue(linter.has_errors)

    @mock.patch(
        "django_migration_linter.MigrationLinter._gather_all_migrations",
        return_value=[
            Migration("0001_create_table", "app_add_not_null_column"),
            Migration("0002_add_new_not_null_field",
                      "app_add_not_null_column"),
        ],
    )
    def test_cache_different_databases(self, *args):
        linter = MigrationLinter(self.test_project_path, database="mysql")
        linter.old_cache.clear()
        linter.old_cache.save()

        linter = MigrationLinter(self.test_project_path, database="sqlite")
        linter.old_cache.clear()
        linter.old_cache.save()

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(2, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("OK",
                         cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"])
        self.assertEqual("ERR",
                         cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"])
        self.assertListEqual(
            cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"],
            [{
                "err_msg": "RENAMING tables",
                "code": "RENAME_TABLE",
                "table": None,
                "column": None,
            }],
        )

        # Start the Linter again but with different database, should not be the same cache
        linter = MigrationLinter(self.test_project_path, database="mysql")

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(2, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("OK",
                         cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"])
        self.assertEqual("ERR",
                         cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"])
        self.assertListEqual(
            cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"],
            [{
                "err_msg": "NOT NULL constraint on columns",
                "code": "NOT_NULL",
                "table": "app_add_not_null_column_a",
                "column": "new_not_null_field",
            }],
        )

        self.assertTrue(linter.has_errors)

    @mock.patch(
        "django_migration_linter.MigrationLinter._gather_all_migrations",
        return_value=[
            Migration("0001_initial", "app_ignore_migration"),
            Migration("0002_ignore_migration", "app_ignore_migration"),
        ],
    )
    def test_cache_ignored(self, *args):
        linter = MigrationLinter(self.test_project_path,
                                 ignore_name_contains="0001")
        linter.old_cache.clear()
        linter.old_cache.save()

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(1, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("IGNORE",
                         cache["0fab48322ba76570da1a3c193abb77b5"]["result"])

        # Start the Linter again -> should use cache now.
        linter = MigrationLinter(self.test_project_path)

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(1, analyse_sql_statements_mock.call_count)

    @mock.patch(
        "django_migration_linter.MigrationLinter._gather_all_migrations",
        return_value=[
            Migration("0002_add_new_not_null_field", "app_add_not_null_column")
        ],
    )
    def test_cache_modified(self, *args):
        linter = MigrationLinter(self.test_project_path)
        linter.old_cache.clear()
        linter.old_cache.save()

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(1, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("ERR",
                         cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"])

        # Get the content of the migration file and mock the open call to append
        # some content to change the hash
        migration_path = get_migration_abspath("app_add_not_null_column",
                                               "0002_add_new_not_null_field")
        with open(migration_path, "rb") as f:
            file_content = f.read()
        file_content += b"# test comment"

        linter = MigrationLinter(self.test_project_path)
        with mock.patch(
                "django_migration_linter.migration_linter.open",
                mock.mock_open(read_data=file_content),
        ):
            with mock.patch(
                    "django_migration_linter.migration_linter.analyse_sql_statements",
                    wraps=analyse_sql_statements,
            ) as analyse_sql_statements_mock:
                linter.lint_all_migrations()
                self.assertEqual(1, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertNotIn("19fd3ea688fc05e2cc2a6e67c0b7aa17", cache)
        self.assertEqual(1, len(cache))
        self.assertEqual("ERR",
                         cache["a25768641a0ad526fad199f97c303784"]["result"])

    @mock.patch(
        "django_migration_linter.MigrationLinter._gather_all_migrations",
        return_value=[
            Migration("0001_create_table", "app_add_not_null_column"),
            Migration("0002_add_new_not_null_field",
                      "app_add_not_null_column"),
        ],
    )
    def test_ignore_cached_migration(self, *args):
        linter = MigrationLinter(self.test_project_path)
        linter.old_cache.clear()
        linter.old_cache.save()

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            self.assertEqual(2, analyse_sql_statements_mock.call_count)

        cache = linter.new_cache
        cache.load()

        self.assertEqual("OK",
                         cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"])
        self.assertEqual("ERR",
                         cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["result"])
        self.assertListEqual(
            cache["19fd3ea688fc05e2cc2a6e67c0b7aa17"]["errors"],
            [{
                "err_msg": "RENAMING tables",
                "code": "RENAME_TABLE",
                "table": None,
                "column": None,
            }],
        )

        # Start the Linter again -> should use cache now but ignore the erroneous
        linter = MigrationLinter(
            self.test_project_path,
            ignore_name_contains="0002_add_new_not_null_field")

        with mock.patch(
                "django_migration_linter.migration_linter.analyse_sql_statements",
                wraps=analyse_sql_statements,
        ) as analyse_sql_statements_mock:
            linter.lint_all_migrations()
            analyse_sql_statements_mock.assert_not_called()

        self.assertFalse(linter.has_errors)

        cache = linter.new_cache
        cache.load()
        self.assertEqual(1, len(cache))
        self.assertEqual("OK",
                         cache["4a3770a405738d457e2d23e17fb1f3aa"]["result"])
Exemple #17
0
    def handle(self, *app_labels, **options):
        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        self.dry_run = options['dry_run']
        self.merge = options['merge']
        self.empty = options['empty']
        self.migration_name = options['name']
        self.replace_all = options['replace_all']
        check_changes = options['check_changes']

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                if '.' in app_label:
                    self.stderr.write(
                        "'%s' is not a valid app label. Did you mean '%s'?" % (
                            app_label,
                            app_label.split('.')[-1],
                        ))
                else:
                    self.stderr.write(
                        "App '%s' could not be found. Is it in INSTALLED_APPS?"
                        % app_label)
            sys.exit(2)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None, ignore_no_migrations=True)

        # Raise an error if any migrations are applied before their dependencies.
        consistency_check_labels = {
            config.label
            for config in apps.get_app_configs()
        }
        # Non-default databases are only checked if database routers used.
        aliases_to_check = connections if settings.DATABASE_ROUTERS else [
            DEFAULT_DB_ALIAS
        ]
        for alias in sorted(aliases_to_check):
            connection = connections[alias]
            if (connection.settings_dict['ENGINE'] !=
                    'django.db.backends.dummy' and any(
                        # At least one model must be migrated to the database.
                        router.allow_migrate(connection.alias,
                                             app_label,
                                             model_name=model._meta.object_name
                                             )
                        for app_label in consistency_check_labels for model in
                        apps.get_app_config(app_label).get_models())):
                loader.check_consistent_history(connection)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()

        # If app_labels is specified, filter out conflicting migrations for unspecified apps
        if app_labels:
            conflicts = {
                app_label: conflict
                for app_label, conflict in conflicts.items()
                if app_label in app_labels
            }

        if conflicts and not self.merge:
            name_str = "; ".join("%s in %s" % (", ".join(names), app)
                                 for app, names in conflicts.items())
            raise CommandError(
                "Conflicting migrations detected; multiple leaf nodes in the "
                "migration graph: (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str)

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        if self.interactive:
            questioner = InteractiveMigrationQuestioner(
                specified_apps=app_labels, dry_run=self.dry_run)
        else:
            questioner = NonInteractiveMigrationQuestioner(
                specified_apps=app_labels, dry_run=self.dry_run)

        if self.replace_all:
            replace_list = [
                migration for migration in loader.graph.nodes.values()
            ]
            temp_nodes = loader.graph.nodes

            loader.graph.nodes = {
                k: v
                for (k, v) in loader.graph.nodes.items()
                if k[0] not in app_labels
            }

            autodetector = MigrationAutodetector(
                loader.project_state(),
                ProjectState.from_apps(apps),
                questioner,
            )

            loader.graph.nodes = temp_nodes

        else:
            autodetector = MigrationAutodetector(
                loader.project_state(),
                ProjectState.from_apps(apps),
                questioner,
            )

        # If they want to make an empty migration, make one for each app
        if self.empty:
            if not app_labels:
                raise CommandError(
                    "You must supply at least one app label when using --empty."
                )
            # Make a fake changes() result we can pass to arrange_for_graph
            changes = {app: [Migration("custom", app)] for app in app_labels}
            changes = autodetector.arrange_for_graph(
                changes=changes,
                graph=loader.graph,
                migration_name=self.migration_name,
            )
            self.write_migration_files(changes)
            return

        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name=self.migration_name,
        )

        if not changes:
            # No changes? Tell them.
            if self.verbosity >= 1:
                if app_labels:
                    if len(app_labels) == 1:
                        self.stdout.write("No changes detected in app '%s'" %
                                          app_labels.pop())
                    else:
                        self.stdout.write("No changes detected in apps '%s'" %
                                          ("', '".join(app_labels)))
                else:
                    self.stdout.write("No changes detected")
        else:
            if self.replace_all:
                for app_label, app_migrations in changes.items():
                    for app_migration in app_migrations:
                        app_migration.replaces = \
                            [
                                (migration.app_label, migration.name)
                                for migration in replace_list
                                if migration.app_label == app_label
                            ]
                        app_migration.dependencies = [
                            dependency
                            for dependency in app_migration.dependencies
                            if dependency not in app_migration.replaces
                        ]

            self.write_migration_files(changes)
            if check_changes:
                sys.exit(1)
Exemple #18
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)
        self.merge = options.get('merge', False)
        self.empty = options.get('empty', False)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                    app_label)
            sys.exit(2)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()
        if conflicts and not self.merge:
            name_str = "; ".join("%s in %s" % (", ".join(names), app)
                                 for app, names in conflicts.items())
            raise CommandError(
                "Conflicting migrations detected (%s).\nTo fix them run 'python manage.py makemigrations --merge'"
                % name_str)

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )

        # If they want to make an empty migration, make one for each app
        if self.empty:
            if not app_labels:
                raise CommandError(
                    "You must supply at least one app label when using --empty."
                )
            # Make a fake changes() result we can pass to arrange_for_graph
            changes = dict(
                (app, [Migration("custom", app)]) for app in app_labels)
            changes = autodetector.arrange_for_graph(changes, loader.graph)
            self.write_migration_files(changes)
            return

        # Detect changes
        changes = autodetector.changes(graph=loader.graph,
                                       trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" %
                                  app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" %
                                  ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        self.write_migration_files(changes)
Exemple #19
0
 def setUp(self):
     self.migration = Migration(app_label="tests", name="migration")
Exemple #20
0
 def setUpClass(cls):
     super().setUpClass()
     cls.pre_deploy = Migration(app_label="tests", name="0001")
     cls.pre_deploy.stage = Stage.PRE_DEPLOY
     cls.post_deploy = Migration(app_label="tests", name="0002")
     cls.post_deploy.stage = Stage.POST_DEPLOY
Exemple #21
0
 def test_stable(self):
     plan = [(Migration("0001_initial", "tests"), True)]
     self.assertEqual(hash_plan(plan),
                      "a4a35230c7d1942265f1bc8f9ce53e05a50848be")
Exemple #22
0
    def handle(self, *app_labels, **options):
        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        self.dry_run = options['dry_run']
        self.merge = options['merge']
        self.empty = options['empty']
        self.migration_name = options['name']
        self.exit_code = options['exit_code']
        check_changes = options['check_changes']

        if self.exit_code:
            warnings.warn(
                "The --exit option is deprecated in favor of the --check option.",
                RemovedInDjango20Warning
            )

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None, ignore_no_migrations=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
        conflicts = loader.detect_conflicts()

        # If app_labels is specified, filter out conflicting migrations for unspecified apps
        if app_labels:
            conflicts = {
                app_label: conflict for app_label, conflict in iteritems(conflicts)
                if app_label in app_labels
            }

        if conflicts and not self.merge:
            name_str = "; ".join(
                "%s in %s" % (", ".join(names), app)
                for app, names in conflicts.items()
            )
            raise CommandError(
                "Conflicting migrations detected; multiple leaf nodes in the "
                "migration graph: (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str
            )

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        if self.interactive:
            questioner = InteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run)
        else:
            questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run)
        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            questioner,
        )

        # If they want to make an empty migration, make one for each app
        if self.empty:
            if not app_labels:
                raise CommandError("You must supply at least one app label when using --empty.")
            # Make a fake changes() result we can pass to arrange_for_graph
            changes = {
                app: [Migration("custom", app)]
                for app in app_labels
            }
            changes = autodetector.arrange_for_graph(
                changes=changes,
                graph=loader.graph,
                migration_name=self.migration_name,
            )
            self.write_migration_files(changes)
            return

        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name=self.migration_name,
        )

        if not changes:
            # No changes? Tell them.
            if self.verbosity >= 1:
                if len(app_labels) == 1:
                    self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
                elif len(app_labels) > 1:
                    self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
                else:
                    self.stdout.write("No changes detected")

            if self.exit_code:
                sys.exit(1)
        else:
            self.write_migration_files(changes)
            if check_changes:
                sys.exit(1)
Exemple #23
0
 def test_app_label(self):
     test_app = (Migration("0001_initial", "tests"), True)
     other_app = (Migration("0001_initial", "other"), True)
     self.assertNotEqual(hash_plan([test_app]), hash_plan([other_app]))
Exemple #24
0
    def handle(self, *app_labels, **options):
        self.verbosity = options["verbosity"]
        self.interactive = options["interactive"]
        self.dry_run = options["dry_run"]
        self.merge = options["merge"]
        self.empty = options["empty"]
        self.migration_name = options["name"]
        if self.migration_name and not self.migration_name.isidentifier():
            raise CommandError(
                "The migration name must be a valid Python identifier.")
        self.include_header = options["include_header"]
        check_changes = options["check_changes"]

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        has_bad_labels = False
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError as err:
                self.stderr.write(str(err))
                has_bad_labels = True
        if has_bad_labels:
            sys.exit(2)

        # Load the current graph state. Pass in None for the connection so
        # the loader doesn't try to resolve replaced migrations from DB.
        loader = MigrationLoader(None, ignore_no_migrations=True)

        # Raise an error if any migrations are applied before their dependencies.
        consistency_check_labels = {
            config.label
            for config in apps.get_app_configs()
        }
        # Non-default databases are only checked if database routers used.
        aliases_to_check = (connections if settings.DATABASE_ROUTERS else
                            [DEFAULT_DB_ALIAS])
        for alias in sorted(aliases_to_check):
            connection = connections[alias]
            if connection.settings_dict[
                    "ENGINE"] != "django.db.backends.dummy" and any(
                        # At least one model must be migrated to the database.
                        router.allow_migrate(connection.alias,
                                             app_label,
                                             model_name=model._meta.object_name
                                             )
                        for app_label in consistency_check_labels for model in
                        apps.get_app_config(app_label).get_models()):
                loader.check_consistent_history(connection)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()

        # If app_labels is specified, filter out conflicting migrations for unspecified apps
        if app_labels:
            conflicts = {
                app_label: conflict
                for app_label, conflict in conflicts.items()
                if app_label in app_labels
            }

        if conflicts and not self.merge:
            name_str = "; ".join("%s in %s" % (", ".join(names), app)
                                 for app, names in conflicts.items())
            raise CommandError(
                "Conflicting migrations detected; multiple leaf nodes in the "
                "migration graph: (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str)

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        if self.interactive:
            questioner = InteractiveMigrationQuestioner(
                specified_apps=app_labels, dry_run=self.dry_run)
        else:
            questioner = NonInteractiveMigrationQuestioner(
                specified_apps=app_labels, dry_run=self.dry_run)
        # Set up autodetector
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            questioner,
        )

        # If they want to make an empty migration, make one for each app
        if self.empty:
            if not app_labels:
                raise CommandError(
                    "You must supply at least one app label when using --empty."
                )
            # Make a fake changes() result we can pass to arrange_for_graph
            changes = {app: [Migration("custom", app)] for app in app_labels}
            changes = autodetector.arrange_for_graph(
                changes=changes,
                graph=loader.graph,
                migration_name=self.migration_name,
            )
            self.write_migration_files(changes)
            return

        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name=self.migration_name,
        )

        if not changes:
            # No changes? Tell them.
            if self.verbosity >= 1:
                if app_labels:
                    if len(app_labels) == 1:
                        self.stdout.write("No changes detected in app '%s'" %
                                          app_labels.pop())
                    else:
                        self.stdout.write("No changes detected in apps '%s'" %
                                          ("', '".join(app_labels)))
                else:
                    self.stdout.write("No changes detected")
        else:
            self.write_migration_files(changes)
            if check_changes:
                sys.exit(1)
Exemple #25
0
 def test_migration_name(self):
     first = (Migration("0001_initial", "tests"), True)
     second = (Migration("0002_second", "tests"), True)
     self.assertNotEqual(hash_plan([first]), hash_plan([second]))