def test_models_match_migrations(self): """Make sure that no model changes exist. This logic is taken from django's makemigrations.py file. Here just detect if model changes exist that require a migration, and if so we fail. """ app_labels = ['django_celery_results'] loader = MigrationLoader(None, ignore_no_migrations=True) questioner = NonInteractiveMigrationQuestioner( specified_apps=app_labels, dry_run=False) autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, ) changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels, convert_apps=app_labels, migration_name='fake_name', ) self.assertTrue( not changes, msg='Model changes exist that do not have a migration')
def make_migration(app_label="tests", from_state=None, to_state=None): """Generates migrations based on the specified app's state.""" app_labels = [app_label] loader = MigrationLoader(None, ignore_no_migrations=True) loader.check_consistent_history(connection) questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=False) autodetector = MigrationAutodetector( from_state or loader.project_state(), to_state or ProjectState.from_apps(apps), questioner, ) changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels or None, convert_apps=app_labels or None, migration_name="test", ) changes_for_app = changes.get(app_label) if not changes_for_app or len(changes_for_app) == 0: return None return changes_for_app[0]
def test_total_deconstruct(self): loader = MigrationLoader(None, load=True, ignore_no_migrations=True) loader.disk_migrations = { t: v for t, v in loader.disk_migrations.items() if t[0] != 'testapp' } app_labels = {"testapp"} questioner = NonInteractiveMigrationQuestioner( specified_apps=app_labels, dry_run=True) autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, ) # Detect changes changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels or None, convert_apps=app_labels or None, migration_name="my_fake_migration_for_test_deconstruct", ) self.assertGreater(len(changes), 0) for app_label, app_migrations in changes.items(): for migration in app_migrations: # Describe the migration writer = MigrationWriter(migration) migration_string = writer.as_string() self.assertNotEqual(migration_string, "")
def make_migration(app_label="tests"): """Generates migrations based on the specified app's state.""" app_labels = [app_label] loader = MigrationLoader(None, ignore_no_migrations=True) loader.check_consistent_history(connection) questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=False) autodetector = MigrationAutodetector(loader.project_state(), ProjectState.from_apps(apps), questioner) changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels or None, convert_apps=app_labels or None, migration_name="test", ) return changes[app_label][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)
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)
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)
def handle(self, *app_labels, **options): self.verbosity = options['verbosity'] self.interactive = options['interactive'] self.dry_run = options['dry_run'] self.empty = options['empty'] self.migration_name = options['name'] # 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) # 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) questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=self.dry_run) ps = ProjectState.from_apps(apps) autodetector = MigrationAutodetector( ps, 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, ) self.write_migration_files(changes)
def handle(self, *args, **options): self.model_name = options['model_name'] self.source_app = options['source_app'] self.dest_app = options['dest_app'] # make sure the apps exist app_labels = set([self.source_app, self.dest_app]) 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( self.style.ERROR( "App '{}' could not be found. Is it in INSTALLED_APPS?" .format(app_label))) sys.exit(2) if len(app_labels) == 1: self.stderr.write( self.style.ERROR( "Cannot move '{}' within the same app '{}'.".format( self.model_name, self.dest_app))) sys.exit(2) # load the current graph loader = MigrationLoader(None, ignore_no_migrations=True) questioner = NonInteractiveMigrationQuestioner() self.from_state = loader.project_state() self.to_state = ProjectState.from_apps(apps) autodetector = MigrationAutodetector( self.from_state, self.to_state, questioner, ) _migrations = [] rename_table = self._get_rename_table_migration() _migrations.append(rename_table) create_model = self._get_create_model_migration([ (rename_table.app_label, rename_table.name), ]) _migrations.append(create_model) model_fk = self._get_model_fk_migrations([ (create_model.app_label, create_model.name), ]) delete_model_deps = [ (rename_table.app_label, rename_table.name), (create_model.app_label, create_model.name), ] for fk_migration in model_fk: _migrations.append(fk_migration) delete_model_deps.append( (fk_migration.app_label, fk_migration.name), ) delete_model = self._get_delete_model_migration(delete_model_deps) _migrations.append(delete_model) changes = {} for migration in _migrations: changes.setdefault(migration.app_label, []).append(migration) changes = autodetector.arrange_for_graph( changes=changes, graph=loader.graph, ) self.write_migration_files(changes) self.stdout.write(self.style.SUCCESS("Done!"))
def handle(self, *app_labels, **options): self.verbosity = options['verbosity'] self.interactive = options['interactive'] self.merge = options['merge'] self.change_name = options['name'] self.dry_run = 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 changes from DB. loader = ChangeLoader(None, ignore_no_changes=True) # Raise an error if any changes 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 changes 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 changes detected; multiple leaf nodes in the ' 'changes graph: (%s).\nTo fix them run ' "'python manage.py makechange --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) # To make an empty change, make one for each app 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: [Change('custom', app)] for app in app_labels } autodetector = ChangeAutodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, ) changes = autodetector.arrange_for_graph( changes=changes, graph=loader.graph, change_name=self.change_name, ) self.write_changes_files(changes)