def test_make_migration_field_operations_view_models(fake_app, define_view_model): """Tests whether field operations against a (materialized) view are always wrapped in the :see:ApplyState operation so that they don't actually get applied to the database, yet Django applies to them to the project state. This is important because you can't actually alter/add or delete fields from a (materialized) view. """ underlying_model = get_fake_model( { "first_name": models.TextField(), "last_name": models.TextField() }, meta_options=dict(app_label=fake_app.name), ) model = define_view_model( fields={"first_name": models.TextField()}, view_options=dict(query=underlying_model.objects.all()), meta_options=dict(app_label=fake_app.name), ) state_1 = ProjectState.from_apps(apps) migration = make_migration(model._meta.app_label) apply_migration(migration.operations, state_1) # add a field to the materialized view last_name_field = models.TextField(null=True) last_name_field.contribute_to_class(model, "last_name") migration = make_migration(model._meta.app_label, from_state=state_1) assert len(migration.operations) == 1 assert isinstance(migration.operations[0], operations.ApplyState) assert isinstance(migration.operations[0].state_operation, AddField) # alter the field on the materialized view state_2 = ProjectState.from_apps(apps) last_name_field = models.TextField(null=True, blank=True) last_name_field.contribute_to_class(model, "last_name") migration = make_migration(model._meta.app_label, from_state=state_2) assert len(migration.operations) == 1 assert isinstance(migration.operations[0], operations.ApplyState) assert isinstance(migration.operations[0].state_operation, AlterField) # remove the field from the materialized view migration = make_migration( model._meta.app_label, from_state=ProjectState.from_apps(apps), to_state=state_1, ) assert isinstance(migration.operations[0], operations.ApplyState) assert isinstance(migration.operations[0].state_operation, RemoveField)
def test_custom_base_manager(self): new_apps = Apps(['migrations']) class Author(models.Model): manager1 = models.Manager() manager2 = models.Manager() class Meta: app_label = 'migrations' apps = new_apps base_manager_name = 'manager2' class Author2(models.Model): manager1 = models.Manager() manager2 = models.Manager() class Meta: app_label = 'migrations' apps = new_apps base_manager_name = 'manager1' project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] self.assertEqual(author_state.options['base_manager_name'], 'manager2') self.assertEqual(author_state.managers, [ ('manager1', Author.manager1), ('manager2', Author.manager2), ]) author2_state = project_state.models['migrations', 'author2'] self.assertEqual(author2_state.options['base_manager_name'], 'manager1') self.assertEqual(author2_state.managers, [ ('manager1', Author2.manager1), ])
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 handle(self, *args, **options): changed = set() self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: sys.exit("Unable to check migrations: cannot connect to database\n") autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changed.update(autodetector.changes(graph=executor.loader.graph).keys()) changed -= set(options['ignore']) if changed: sys.exit( "Apps with model changes but no corresponding migration file: %(changed)s\n" % { 'changed': list(changed) }) else: sys.stdout.write("All migration files present\n")
def handle(self, *args, **kwargs): changed = set() ignore_list = ['authtools'] # dependencies that we don't care about migrations for (usually for testing only) self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: sys.exit("Unable to check migrations: cannot connect to database\n") autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changed.update(autodetector.changes(graph=executor.loader.graph).keys()) for ignore in ignore_list: if ignore in changed: changed.remove(ignore) if changed: sys.exit("Apps with model changes but no corresponding migration file: %(changed)s\n" % { 'changed': list(changed) }) else: sys.stdout.write("All migration files present\n")
def check_migrations(): import django django.setup() from django.apps import apps from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.executor import MigrationExecutor from django.db.migrations.state import ProjectState executor = MigrationExecutor(connection=None) conflicts = executor.loader.detect_conflicts() if conflicts: name_str = "; ".join("%s in %s" % (", ".join(names), app) for app, names in conflicts.items()) sys.stdout.write( "Conflicting migrations detected; multiple leaf nodes in the " "migration graph: (%s).\nTo fix them run " "'python manage.py makemigrations --merge'" % name_str) return conflicts autodetector = MigrationAutodetector(executor.loader.project_state(), ProjectState.from_apps(apps)) changes = autodetector.changes(graph=executor.loader.graph) if changes: sys.stdout.write("Your models have changes that are not yet reflected " "in a migration.") return changes
def test_custom_base_manager(self): new_apps = Apps(['migrations']) class Author(models.Model): manager1 = models.Manager() manager2 = models.Manager() class Meta: app_label = 'migrations' apps = new_apps base_manager_name = 'manager2' class Author2(models.Model): manager1 = models.Manager() manager2 = models.Manager() class Meta: app_label = 'migrations' apps = new_apps base_manager_name = 'manager1' project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] self.assertEqual(author_state.options['base_manager_name'], 'manager2') self.assertEqual(author_state.managers, [ ('manager1', Author.manager1), ('manager2', Author.manager2), ]) author2_state = project_state.models['migrations', 'author2'] self.assertEqual(author2_state.options['base_manager_name'], 'manager1') self.assertEqual(author2_state.managers, [ ('manager1', Author2.manager1), ])
def handle(self, *args, **options): changed = set() self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: sys.exit( "Unable to check migrations: cannot connect to database\n") autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changed.update( autodetector.changes(graph=executor.loader.graph).keys()) changed -= set(options["ignore"]) if changed: sys.exit( "Apps with model changes but no corresponding migration file: %(changed)s\n" % {"changed": list(changed)}) else: sys.stdout.write("All migration files present\n")
def check_migrations(): from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.executor import MigrationExecutor from django.db.migrations.state import ProjectState changed = set() print("Checking {} migrations...".format(APP_NAME)) for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError as e: sys.exit( "Unable to check migrations due to database: {}".format(e)) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changed.update( autodetector.changes(graph=executor.loader.graph).keys()) if changed and APP_NAME in changed: sys.exit("A migration file is missing. Please run " "`python makemigrations.py` to generate it.") else: print("All migration files present.")
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 migrate(self, ops, state=None): class Migration(migrations.Migration): operations = ops migration = Migration('name', 'tests') inject_trigger_operations([(migration, False)]) with connection.schema_editor() as schema_editor: return migration.apply(state or ProjectState.from_apps(self.apps), schema_editor)
def handle(self, *args, **kwargs): changed = set() ignore_list = [ 'authtools' ] # dependencies that we don't care about migrations for (usually for testing only) self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: sys.exit( "Unable to check migrations: cannot connect to database\n") autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changed.update( autodetector.changes(graph=executor.loader.graph).keys()) for ignore in ignore_list: if ignore in changed: changed.remove(ignore) if changed: sys.exit( "Apps with model changes but no corresponding migration file: %(changed)s\n" % {'changed': list(changed)}) else: sys.stdout.write("All migration files present\n")
def write_migrations(): import django.db.migrations.loader old_load_disk = django.db.migrations.loader.MigrationLoader.load_disk def load_disk(self): old_load_disk(self) self.disk_migrations.update(existing_migrations()) django.db.migrations.loader.MigrationLoader.load_disk = load_disk loader = MigrationLoader(None, ignore_no_migrations=True) questioner = NonInteractiveMigrationQuestioner() autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, ) app_label = 'meta' app_labels = {app_label, } changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels, convert_apps=app_labels, migration_name=None, ) write_migration_rows(changes)
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_no_duplicate_managers(self): """ When a manager is added with `use_in_migrations = True` and a parent model had a manager with the same name and `use_in_migrations = True`, the parent's manager shouldn't appear in the model state (#26881). """ new_apps = Apps(['migrations']) class PersonManager(models.Manager): use_in_migrations = True class Person(models.Model): objects = PersonManager() class Meta: abstract = True class BossManager(PersonManager): use_in_migrations = True class Boss(Person): objects = BossManager() class Meta: app_label = 'migrations' apps = new_apps project_state = ProjectState.from_apps(new_apps) boss_state = project_state.models['migrations', 'boss'] self.assertEqual(boss_state.managers, [('objects', Boss.objects)])
def run(self, *args, **kwargs): changed = set() log.info("Checking DB migrations") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: log.critical( "Unable to check migrations, cannot connect to database") sys.exit(1) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps)) changed.update( autodetector.changes(graph=executor.loader.graph).keys()) if changed: log.critical("Apps with model changes but no corresponding " f"migration file: {list(changed)}") sys.exit(1) else: log.info("All migration files present")
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 handle(self, *args, **kwargs): changed = set() self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: self.stdout.write("Unable to check migrations: cannot connect to database '{}'.\n".format(db)) sys.exit(1) all_apps = apps.app_configs.keys() questioner = InteractiveMigrationQuestioner(specified_apps=all_apps, dry_run=True) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), questioner, ) changed.update(autodetector.changes(graph=executor.loader.graph, convert_apps=all_apps).keys()) if changed: self.stdout.write( "Apps with model changes but no corresponding migration file: {!r}\n".format( list(changed) ) ) sys.exit(1) else: self.stdout.write("All migration files present.\n") sys.exit(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 check_missing_migrations(): """Check that user model and migration files are in sync""" from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.loader import MigrationLoader from django.db.migrations.questioner import ( NonInteractiveMigrationQuestioner as Questioner, ) from django.db.migrations.state import ProjectState loader = MigrationLoader(None, ignore_no_migrations=True) conflicts = loader.detect_conflicts() if conflicts: raise Exception( "Migration conflicts detected. Please fix your migrations.") questioner = Questioner(dry_run=True, specified_apps=None) autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, ) changes = autodetector.changes( graph=loader.graph, trim_to_apps=None, convert_apps=None, migration_name=None, ) if changes: raise Exception( "Migration changes detected. " "Please update or add to the migration file as appropriate") print("Migration-checker detected no problems.")
def handle(self, *args, **kwargs): changed = set() self.stdout.write("Checking...") for db in settings.DATABASES.keys(): try: executor = MigrationExecutor(connections[db]) except OperationalError: self.stdout.write("Unable to check migrations: cannot connect to database '{}'.\n".format(db)) sys.exit(1) all_apps = apps.app_configs.keys() questioner = InteractiveMigrationQuestioner(specified_apps=all_apps, dry_run=True) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), questioner, ) changed.update(autodetector.changes(graph=executor.loader.graph, convert_apps=all_apps).keys()) if changed: self.stdout.write( "Apps with model changes but no corresponding migration file: {!r}\n".format( list(changed) ) ) sys.exit(1) else: self.stdout.write("All migration files present.\n") sys.exit(0)
def test_missing_migrations(self): executor = MigrationExecutor(connection) autodetector = MigrationAutodetector(executor.loader.project_state(), ProjectState.from_apps(apps)) changes = autodetector.changes(graph=executor.loader.graph) self.assertEqual({}, changes)
def write_migration(self, migration): loader = MigrationLoader(None, ignore_no_migrations=True) autodetector = MigrationAutodetector(loader.project_state(), ProjectState.from_apps(apps),) changes = autodetector.arrange_for_graph(changes={'share': [migration]}, graph=loader.graph,) for m in changes['share']: writer = MigrationWriter(m) with open(writer.path, 'wb') as fp: fp.write(writer.as_string())
def test_for_missing_migrations(self): """Checks if there're models changes which aren't reflected in migrations.""" migrations_loader = MigrationExecutor(connection).loader migrations_detector = MigrationAutodetector( from_state=migrations_loader.project_state(), to_state=ProjectState.from_apps(apps)) if migrations_detector.changes(graph=migrations_loader.graph): self.fail('Your models have changes that are not yet reflected ' 'in a migration. You should add them now.')
def test_choices_iterator(self): """ #24483 - ProjectState.from_apps should not destructively consume Field.choices iterators. """ new_apps = Apps(["migrations"]) choices = [('a', 'A'), ('b', 'B')] class Author(models.Model): name = models.CharField(max_length=255) choice = models.CharField(max_length=255, choices=iter(choices)) class Meta: app_label = "migrations" apps = new_apps ProjectState.from_apps(new_apps) choices_field = Author._meta.get_field('choice') self.assertEqual(list(choices_field.choices), choices)
def test_choices_iterator(self): """ #24483 - ProjectState.from_apps should not destructively consume Field.choices iterators. """ new_apps = Apps(["migrations"]) choices = [('a', 'A'), ('b', 'B')] class Author(models.Model): name = models.CharField(max_length=255) choice = models.CharField(max_length=255, choices=iter(choices)) class Meta: app_label = "migrations" apps = new_apps ProjectState.from_apps(new_apps) choices_field = Author._meta.get_field('choice') self.assertEqual(list(choices_field.choices), choices)
def test_missing_migrations(self): executor = MigrationExecutor(connection) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) self.assertEqual({}, changes)
def test_for_missing_migrations(self): """Checks if there're models changes which aren't reflected in migrations.""" migrations_loader = MigrationExecutor(connection).loader migrations_detector = MigrationAutodetector( from_state=migrations_loader.project_state(), to_state=ProjectState.from_apps(apps) ) if migrations_detector.changes(graph=migrations_loader.graph): self.fail( 'Your models have changes that are not yet reflected ' 'in a migration. You should add them now.' )
def test_missing_migrations(self): from django.db import connection from django.apps.registry import apps from django.db.migrations.executor import MigrationExecutor executor = MigrationExecutor(connection) from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.state import ProjectState autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) self.assertEqual({}, changes)
def test_for_missing_migrations(self): """Checks if there're models changes which aren't reflected in migrations.""" current_models_state = ProjectState.from_apps(apps) # skip tracking changes for TestModel current_models_state.remove_model('elasticsearch_django', 'testmodel') migrations_loader = MigrationExecutor(connection).loader migrations_detector = MigrationAutodetector( from_state=migrations_loader.project_state(), to_state=current_models_state) if migrations_detector.changes(graph=migrations_loader.graph): self.fail('Your models have changes that are not yet reflected ' 'in a migration. You should add them now.')
def test_missing_migrations(self, deactivate_locale): from django.db import connection from django.apps.registry import apps from django.db.migrations.executor import MigrationExecutor executor = MigrationExecutor(connection) from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.state import ProjectState autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) # noinspection PyUnresolvedReferences assert changes == {}
def handle(self, *app_labels, **options): # get supported options. self.verbosity = options['verbosity'] self.dry_run = options['dry_run'] self.merge = options['merge'] self.migration_name = options['name'] self.include_header = options['include_header'] check_changes = options['check_changes'] # validation application labels app_labels = set(app_labels) self.validate_applications(app_labels) # we don't check conflicts as regular makemigrations command. # we don't check if any migrations are applied before their dependencies as regular makemigrations command. # load migrations using same loader as in regular command loader = MigrationLoader(None, ignore_no_migrations=True) from_state = loader.project_state() to_state = ProjectState.from_apps(apps) # overwritten autodetector. They detect only view changes. autodetector = ViewMigrationAutoDetector( from_state, to_state, ) changes = autodetector.changes( graph=loader.graph, trim_to_apps=app_labels or None, convert_apps=app_labels or None, migration_name=self.migration_name, ) # it's copy paste from make migration command 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") else: self.write_migration_files(changes)
def write_migration(self, migration): loader = MigrationLoader(None, ignore_no_migrations=True) autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.arrange_for_graph( changes={'share': [migration]}, graph=loader.graph, ) for m in changes['share']: writer = MigrationWriter(m) with open(writer.path, 'wb') as fp: fp.write(writer.as_string())
def test_for_missing_migrations(self): """Checks if there're models changes which aren't reflected in migrations.""" current_models_state = ProjectState.from_apps(apps) # skip test models current_models_state.remove_model('onfido', 'testbasemodel') current_models_state.remove_model('onfido', 'testbasestatusmodel') migrations_loader = MigrationExecutor(connection).loader migrations_detector = MigrationAutodetector( from_state=migrations_loader.project_state(), to_state=current_models_state) # import pdb; pdb.set_trace() if migrations_detector.changes(graph=migrations_loader.graph): self.fail('Your models have changes that are not yet reflected ' 'in a migration. You should add them now.')
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)
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)
def load_items(connection: BaseDatabaseWrapper, **kwargs) -> None: """ This function provides a way to load items into memory on server startup from the target database. It unregisters itself to ensure that it is only run once per startup. :param connection: a Django BaseDatabaseWrapper object :param kwargs: additional keyword arguments :return: None """ connection = connections[DEFAULT_DB_ALIAS] connection.prepare_database() executor = MigrationExecutor(connection) targets = executor.loader.graph.leaf_nodes() autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) unmade_migrations = autodetector.changes(graph=executor.loader.graph) unrun_migrations = executor.migration_plan(targets) all_migrations_run = True # If there are any unmade migrations, we can't safely load the items into memory for app in unmade_migrations: if APP_NAME in str(app): print("Can't load items. Detected unmade migrations.") all_migrations_run = False break # If there aren't unmade migrations, check if any migrations still need applied if all_migrations_run: for migration, _ in unrun_migrations: if APP_NAME in str(migration): print("Can't load items. Migrations need applied.") all_migrations_run = False break # If all migrations are run and there aren't unmade migrations, we load the items into memory if all_migrations_run: for item in Item.objects.all(): items[item.id] = item # We do this to ensure this runs ONLY once per startup connection_created.disconnect(load_items)
def make_migrations(self): """Runs the auto-detector and detects changes in the project that can be put in a migration.""" new_project_state = ProjectState.from_apps(self.apps) autodetector = MigrationAutodetector(self.project_state, new_project_state) changes = autodetector._detect_changes() migrations = changes.get('tests', []) migration = migrations[0] if len(migrations) > 0 else None self.migrations.append(migration) self.project_state = new_project_state return migration
def test_custom_default_manager_named_objects_with_false_migration_flag(self): """ When a manager is added with a name of 'objects' but it does not have `use_in_migrations = True`, no migration should be added to the model state (#26643). """ new_apps = Apps(['migrations']) class Author(models.Model): objects = models.Manager() class Meta: app_label = 'migrations' apps = new_apps project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] self.assertEqual(author_state.managers, [])
def test_custom_default_manager_named_objects_with_false_migration_flag(self): """ When a manager is added with a name of 'objects' but it does not have `use_in_migrations = True`, no migration should be added to the model state (#26643). """ new_apps = Apps(['migrations']) class Author(models.Model): objects = models.Manager() class Meta: app_label = 'migrations' apps = new_apps project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] self.assertEqual(author_state.managers, [])
def test_no_migration_left(self): loader = MigrationLoader(None, ignore_no_migrations=True) conflicts = loader.detect_conflicts() app_labels = ['cities_light'] autodetector = MigrationAutodetector( loader.project_state(), ProjectState.from_apps(apps), InteractiveMigrationQuestioner(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, ) assert 'cities_light' not in changes
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)
def test_custom_default_manager_added_to_the_model_state(self): """ When the default manager of the model is a custom manager, it needs to be added to the model state. """ new_apps = Apps(['migrations']) custom_manager = models.Manager() class Author(models.Model): objects = models.TextField() authors = custom_manager class Meta: app_label = 'migrations' apps = new_apps project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] self.assertEqual(author_state.managers, [('authors', custom_manager)])
def handle(self, *args, **options): """ Detect model changes without migrations. The implementation here is a very stripped down version of the `migrate` management command in django core. """ connection = connections[DEFAULT_DB_ALIAS] executor = MigrationExecutor(connection) autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: message = ( "Your models have changes that are not yet reflected " + "in a migration. Run 'manage.py makemigrations' to make " + "new migrations." ) raise CommandError(message)
def start_operation(self, operation, detonate=True): # Make a from_state and a to_state to pass to the operation, these can just be the # current state of the models from_state = ProjectState.from_apps(apps) to_state = from_state.clone() schema_editor = connection.schema_editor() app_label = TestModel._meta.app_label # If we just start the operation then it will hang forever waiting for its mapper task to # complete, so we won't even be able to call process_task_queues(). So to avoid that we # detonate the _wait_until_task_finished method. Then tasks can be processed after that. if detonate: with sleuth.detonate( "djangae.tests.test_migrations.operations.%s._wait_until_task_finished" % operation.__class__.__name__, UniqueException ): try: operation.database_forwards(app_label, schema_editor, from_state, to_state) except UniqueException: pass else: operation.database_forwards(app_label, schema_editor, from_state, to_state)
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 test_create(self): """ Tests making a ProjectState from an Apps """ new_apps = Apps(["migrations"]) class Author(models.Model): name = models.CharField(max_length=255) bio = models.TextField() age = models.IntegerField(blank=True, null=True) class Meta: app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] index_together = ["bio", "age"] class AuthorProxy(Author): class Meta: app_label = "migrations" apps = new_apps proxy = True ordering = ["name"] class SubAuthor(Author): width = models.FloatField(null=True) class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author, models.CASCADE) contributors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps verbose_name = "tome" db_table = "test_tome" class Food(models.Model): food_mgr = FoodManager('a', 'b') food_qs = FoodQuerySet.as_manager() food_no_mgr = NoMigrationFoodManager('x', 'y') class Meta: app_label = "migrations" apps = new_apps class FoodNoManagers(models.Model): class Meta: app_label = "migrations" apps = new_apps class FoodNoDefaultManager(models.Model): food_no_mgr = NoMigrationFoodManager('x', 'y') food_mgr = FoodManager('a', 'b') food_qs = FoodQuerySet.as_manager() class Meta: app_label = "migrations" apps = new_apps mgr1 = FoodManager('a', 'b') mgr2 = FoodManager('x', 'y', c=3, d=4) class FoodOrderedManagers(models.Model): # The managers on this model should be ordered by their creation # counter and not by the order in model body food_no_mgr = NoMigrationFoodManager('x', 'y') food_mgr2 = mgr2 food_mgr1 = mgr1 class Meta: app_label = "migrations" apps = new_apps project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] author_proxy_state = project_state.models['migrations', 'authorproxy'] sub_author_state = project_state.models['migrations', 'subauthor'] book_state = project_state.models['migrations', 'book'] food_state = project_state.models['migrations', 'food'] food_no_managers_state = project_state.models['migrations', 'foodnomanagers'] food_no_default_manager_state = project_state.models['migrations', 'foodnodefaultmanager'] food_order_manager_state = project_state.models['migrations', 'foodorderedmanagers'] self.assertEqual(author_state.app_label, "migrations") self.assertEqual(author_state.name, "Author") self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"]) self.assertEqual(author_state.fields[1][1].max_length, 255) self.assertIs(author_state.fields[2][1].null, False) self.assertIs(author_state.fields[3][1].null, True) self.assertEqual( author_state.options, {"unique_together": {("name", "bio")}, "index_together": {("bio", "age")}} ) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(book_state.app_label, "migrations") self.assertEqual(book_state.name, "Book") self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertIs(book_state.fields[2][1].null, False) self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField") self.assertEqual(book_state.options, {"verbose_name": "tome", "db_table": "test_tome"}) self.assertEqual(book_state.bases, (models.Model, )) self.assertEqual(author_proxy_state.app_label, "migrations") self.assertEqual(author_proxy_state.name, "AuthorProxy") self.assertEqual(author_proxy_state.fields, []) self.assertEqual(author_proxy_state.options, {"proxy": True, "ordering": ["name"]}) self.assertEqual(author_proxy_state.bases, ("migrations.author", )) self.assertEqual(sub_author_state.app_label, "migrations") self.assertEqual(sub_author_state.name, "SubAuthor") self.assertEqual(len(sub_author_state.fields), 2) self.assertEqual(sub_author_state.bases, ("migrations.author", )) # The default manager is used in migrations self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr']) self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_state.managers)) self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2)) # No explicit managers defined. Migrations will fall back to the default self.assertEqual(food_no_managers_state.managers, []) # food_mgr is used in migration but isn't the default mgr, hence add the # default self.assertEqual([name for name, mgr in food_no_default_manager_state.managers], ['food_no_mgr', 'food_mgr']) self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_no_default_manager_state.managers)) self.assertEqual(food_no_default_manager_state.managers[0][1].__class__, models.Manager) self.assertIsInstance(food_no_default_manager_state.managers[1][1], FoodManager) self.assertEqual([name for name, mgr in food_order_manager_state.managers], ['food_mgr1', 'food_mgr2']) self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_order_manager_state.managers)) self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers], [('a', 'b', 1, 2), ('x', 'y', 3, 4)])
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, dry_run=self.dry_run), ) # 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, convert_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)
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) # 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. Takes a connection, but it's not used # (makemigrations doesn't look at the database state). # Also make sure the graph is built without unmigrated apps shoehorned in. loader = MigrationLoader(connections[DEFAULT_DB_ALIAS]) loader.build_graph(ignore_unmigrated=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 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) # Detect changes autodetector = MigrationAutodetector( loader.graph.project_state(), ProjectState.from_apps(apps), InteractiveMigrationQuestioner(specified_apps=app_labels), ) 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 directory_created = {} for app_label, app_migrations in changes.items(): if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n") for migration in app_migrations: # Describe the migration writer = MigrationWriter(migration) if self.verbosity >= 1: self.stdout.write(" %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),)) for operation in migration.operations: self.stdout.write(" - %s\n" % operation.describe()) # Write it if not self.dry_run: migrations_directory = os.path.dirname(writer.path) if not directory_created.get(app_label, False): if not os.path.isdir(migrations_directory): os.mkdir(migrations_directory) init_path = os.path.join(migrations_directory, "__init__.py") if not os.path.isfile(init_path): open(init_path, "w").close() # We just do this once per app directory_created[app_label] = True migration_string = writer.as_string() with open(writer.path, "wb") as fh: fh.write(migration_string)
def _get_state(models): project_state = ProjectState.from_apps(apps) for model in models: model_state = ModelState.from_model(model) project_state.add_model(model_state) return project_state
def handle(self, *args, **options): self.verbosity = options.get('verbosity') self.interactive = options.get('interactive') # Import the 'management' module within each installed app, to register # dispatcher events. for app_config in apps.get_app_configs(): if module_has_submodule(app_config.module, "management"): import_module('.management', app_config.name) # Get the database we're operating from db = options.get('database') connection = connections[db] # If they asked for a migration listing, quit main execution flow and show it if options.get("list", False): warnings.warn( "The 'migrate --list' command is deprecated. Use 'showmigrations' instead.", RemovedInDjango110Warning, stacklevel=2) self.stdout.ending = None # Remove when #21429 is fixed return call_command( 'showmigrations', '--list', app_labels=[options['app_label']] if options['app_label'] else None, database=db, no_color=options.get('no_color'), settings=options.get('settings'), stdout=self.stdout, traceback=options.get('traceback'), verbosity=self.verbosity, ) # Hook for backends needing any database preparation connection.prepare_database() # Work out which apps have migrations and which do not executor = MigrationExecutor(connection, self.migration_progress_callback) # Before anything else, see if there's conflicting apps and drop out # hard if there are any conflicts = executor.loader.detect_conflicts() if conflicts: 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 supplied command line arguments, work out what they mean. target_app_labels_only = True if options['app_label'] and options['migration_name']: app_label, migration_name = options['app_label'], options['migration_name'] if app_label not in executor.loader.migrated_apps: raise CommandError( "App '%s' does not have migrations." % app_label ) if migration_name == "zero": targets = [(app_label, None)] else: try: migration = executor.loader.get_migration_by_prefix(app_label, migration_name) except AmbiguityError: raise CommandError( "More than one migration matches '%s' in app '%s'. " "Please be more specific." % (migration_name, app_label) ) except KeyError: raise CommandError("Cannot find a migration matching '%s' from app '%s'." % ( migration_name, app_label)) targets = [(app_label, migration.name)] target_app_labels_only = False elif options['app_label']: app_label = options['app_label'] if app_label not in executor.loader.migrated_apps: raise CommandError( "App '%s' does not have migrations." % app_label ) targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label] else: targets = executor.loader.graph.leaf_nodes() plan = executor.migration_plan(targets) run_syncdb = options.get('run_syncdb') and executor.loader.unmigrated_apps # Print some useful info if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:")) if run_syncdb: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + (", ".join(executor.loader.unmigrated_apps)) ) if target_app_labels_only: self.stdout.write( self.style.MIGRATE_LABEL(" Apply all migrations: ") + (", ".join(set(a for a, n in targets)) or "(none)") ) else: if targets[0][1] is None: self.stdout.write(self.style.MIGRATE_LABEL( " Unapply all migrations: ") + "%s" % (targets[0][0], ) ) else: self.stdout.write(self.style.MIGRATE_LABEL( " Target specific migration: ") + "%s, from %s" % (targets[0][1], targets[0][0]) ) emit_pre_migrate_signal(self.verbosity, self.interactive, connection.alias) # Run the syncdb phase. if run_syncdb: if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:")) self.sync_apps(connection, executor.loader.unmigrated_apps) # Migrate! if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:")) if not plan: executor.check_replacements() if self.verbosity >= 1: self.stdout.write(" No migrations to apply.") # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: self.stdout.write(self.style.NOTICE( " Your models have changes that are not yet reflected " "in a migration, and so won't be applied." )) self.stdout.write(self.style.NOTICE( " Run 'manage.py makemigrations' to make new " "migrations, and then re-run 'manage.py migrate' to " "apply them." )) else: fake = options.get("fake") fake_initial = options.get("fake_initial") executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial) # Send the post_migrate signal, so individual apps can do whatever they need # to do at this point. emit_post_migrate_signal(self.verbosity, self.interactive, connection.alias)
import os
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'] 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: 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) # 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, *args, **options): self.verbosity = verbosity = options.get('verbosity') self.interactive = interactive = options.get('interactive') migrations_dir = options.get('input_dir') try: default_input_dir = os.path.join( settings.BASE_DIR, DEFAULT_PENDING_MIGRATIONS_DIRECTORY) except AttributeError: default_input_dir = None if migrations_dir is None: if not default_input_dir: raise CommandError( "No input directory to read migrations from. Either set " "BASE_DIR in your settings or provide a directory path " "via the --input-dir option.") else: migrations_dir = default_input_dir elif not migrations_dir: raise CommandError( "Provide a real directory path via the --input-dir option.") if not (os.path.exists(migrations_dir) and os.listdir(migrations_dir)): raise CommandError("Input directory (%s) doesn't exist or is " "empty." % migrations_dir) # Get the database we're operating from db = options.get('database') connection = connections[db] # Hook for backends needing any database preparation try: connection.prepare_database() except AttributeError: # pragma: no cover pass executor = MigrationExecutor(connection, self.migration_progress_callback) # Replace the loader with a pending migration one executor.loader = PendingMigrationLoader( connection, pending_migrations_dir=migrations_dir) targets = executor.loader.graph.leaf_nodes() pending_migration_keys = executor.loader.pending_migrations.keys() if options.get('unapply'): targets = [] # We only want to unapply the collected migrations for key, migration in executor.loader.pending_migrations.items(): app_label, migration_name = key migration_found = False for dependency in migration.dependencies: pending = dependency in pending_migration_keys if dependency[0] == app_label and not pending: result = executor.loader.check_key(dependency, app_label) dependency = result or dependency targets.append(dependency) migration_found = True if not migration_found: targets.append((app_label, None)) else: # Trim non-collected migrations for migration_key in list(targets): if migration_key not in pending_migration_keys: targets.remove(migration_key) plan = executor.migration_plan(targets) MIGRATE_HEADING = self.style.MIGRATE_HEADING MIGRATE_LABEL = self.style.MIGRATE_LABEL # Print some useful info if verbosity > 0: self.stdout.write(MIGRATE_HEADING("Operations to perform:")) for target in targets: if target[1] is None: self.stdout.write(MIGRATE_LABEL( " Unapply all migrations: ") + "%s" % (target[0],) ) else: self.stdout.write(MIGRATE_LABEL( " Target specific migration: ") + "%s, from %s" % (target[1], target[0]) ) try: # pragma: no cover emit_pre_migrate_signal([], verbosity, interactive, connection.alias) except TypeError: # pragma: no cover emit_pre_migrate_signal(verbosity, interactive, connection.alias) # Migrate! if verbosity > 0: self.stdout.write(MIGRATE_HEADING("Running migrations:")) if not plan: if verbosity > 0: self.stdout.write(" No migrations to apply.") # If there's changes not in migrations, tell them how to fix it autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: self.stdout.write(self.style.NOTICE( " Your models have changes that are not yet reflected" " in a migration, and so won't be applied." )) self.stdout.write(self.style.NOTICE( " Run 'manage.py makeprojectmigrations' to make new " "migrations, and then run 'manage.py migrateproject' " "to apply them." )) else: executor.migrate(targets, plan, fake=options.get("fake", False)) # A little database clean-up for app_label, migration_name in pending_migration_keys: executor.recorder.record_unapplied(app_label, migration_name) # Send the post_migrate signal, so individual apps can do whatever they # need to do at this point. try: # pragma: no cover emit_post_migrate_signal([], verbosity, interactive, connection.alias) except TypeError: # pragma: no cover emit_post_migrate_signal(verbosity, interactive, connection.alias)
def handle(self, *args, **options): self.verbosity = options['verbosity'] self.interactive = options['interactive'] # Import the 'management' module within each installed app, to register # dispatcher events. for app_config in apps.get_app_configs(): if module_has_submodule(app_config.module, "management"): import_module('.management', app_config.name) # Get the database we're operating from db = options['database'] connection = connections[db] # Hook for backends needing any database preparation connection.prepare_database() # Work out which apps have migrations and which do not executor = MigrationExecutor(connection, self.migration_progress_callback) # Raise an error if any migrations are applied before their dependencies. executor.loader.check_consistent_history(connection) # Before anything else, see if there's conflicting apps and drop out # hard if there are any conflicts = executor.loader.detect_conflicts() if conflicts: 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 supplied command line arguments, work out what they mean. target_app_labels_only = True if options['app_label']: # Validate app_label. app_label = options['app_label'] try: apps.get_app_config(app_label) except LookupError as err: raise CommandError(str(err)) if app_label not in executor.loader.migrated_apps: raise CommandError("App '%s' does not have migrations." % app_label) if options['app_label'] and options['migration_name']: migration_name = options['migration_name'] if migration_name == "zero": targets = [(app_label, None)] else: try: migration = executor.loader.get_migration_by_prefix(app_label, migration_name) except AmbiguityError: raise CommandError( "More than one migration matches '%s' in app '%s'. " "Please be more specific." % (migration_name, app_label) ) except KeyError: raise CommandError("Cannot find a migration matching '%s' from app '%s'." % ( migration_name, app_label)) targets = [(app_label, migration.name)] target_app_labels_only = False elif options['app_label']: targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label] else: targets = executor.loader.graph.leaf_nodes() plan = executor.migration_plan(targets) if options['plan']: self.stdout.write('Planned operations:', self.style.MIGRATE_LABEL) if not plan: self.stdout.write(' No planned migration operations.') for migration, backwards in plan: self.stdout.write(str(migration), self.style.MIGRATE_HEADING) for operation in migration.operations: message, is_error = self.describe_operation(operation, backwards) style = self.style.WARNING if is_error else None self.stdout.write(' ' + message, style) return run_syncdb = options['run_syncdb'] and executor.loader.unmigrated_apps # Print some useful info if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:")) if run_syncdb: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + (", ".join(sorted(executor.loader.unmigrated_apps))) ) if target_app_labels_only: self.stdout.write( self.style.MIGRATE_LABEL(" Apply all migrations: ") + (", ".join(sorted({a for a, n in targets})) or "(none)") ) else: if targets[0][1] is None: self.stdout.write(self.style.MIGRATE_LABEL( " Unapply all migrations: ") + "%s" % (targets[0][0],) ) else: self.stdout.write(self.style.MIGRATE_LABEL( " Target specific migration: ") + "%s, from %s" % (targets[0][1], targets[0][0]) ) pre_migrate_state = executor._create_project_state(with_applied_migrations=True) pre_migrate_apps = pre_migrate_state.apps emit_pre_migrate_signal( self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan, ) # Run the syncdb phase. if run_syncdb: if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:")) self.sync_apps(connection, executor.loader.unmigrated_apps) # Migrate! if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:")) if not plan: if self.verbosity >= 1: self.stdout.write(" No migrations to apply.") # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: self.stdout.write(self.style.NOTICE( " Your models have changes that are not yet reflected " "in a migration, and so won't be applied." )) self.stdout.write(self.style.NOTICE( " Run 'manage.py makemigrations' to make new " "migrations, and then re-run 'manage.py migrate' to " "apply them." )) fake = False fake_initial = False else: fake = options['fake'] fake_initial = options['fake_initial'] post_migrate_state = executor.migrate( targets, plan=plan, state=pre_migrate_state.clone(), fake=fake, fake_initial=fake_initial, ) # post_migrate signals have access to all models. Ensure that all models # are reloaded in case any are delayed. post_migrate_state.clear_delayed_apps_cache() post_migrate_apps = post_migrate_state.apps # Re-render models of real apps to include relationships now that # we've got a final state. This wouldn't be necessary if real apps # models were rendered with relationships in the first place. with post_migrate_apps.bulk_update(): model_keys = [] for model_state in post_migrate_apps.real_models: model_key = model_state.app_label, model_state.name_lower model_keys.append(model_key) post_migrate_apps.unregister_model(*model_key) post_migrate_apps.render_multiple([ ModelState.from_model(apps.get_model(*model)) for model in model_keys ]) # Send the post_migrate signal, so individual apps can do whatever they need # to do at this point. emit_post_migrate_signal( self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan, )
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) # Raise an error if any migrations are applied before their dependencies. for db in connections: loader.check_consistent_history(connections[db]) # 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, *args, **options): self.verbosity = options.get('verbosity') self.interactive = options.get('interactive') self.show_traceback = options.get('traceback') self.load_initial_data = options.get('load_initial_data') # Import the 'management' module within each installed app, to register # dispatcher events. for app_config in apps.get_app_configs(): if module_has_submodule(app_config.module, "management"): import_module('.management', app_config.name) # Get the database we're operating from db = options.get('database') connection = connections[db] # If they asked for a migration listing, quit main execution flow and show it if options.get("list", False): return self.show_migration_list(connection, [options['app_label']] if options['app_label'] else None) # Work out which apps have migrations and which do not executor = MigrationExecutor(connection, self.migration_progress_callback) # Before anything else, see if there's conflicting apps and drop out # hard if there are any conflicts = executor.loader.detect_conflicts() if conflicts: 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 supplied command line arguments, work out what they mean. run_syncdb = False target_app_labels_only = True if options['app_label'] and options['migration_name']: app_label, migration_name = options['app_label'], options['migration_name'] if app_label not in executor.loader.migrated_apps: raise CommandError( "App '%s' does not have migrations (you cannot selectively " "sync unmigrated apps)" % app_label ) if migration_name == "zero": targets = [(app_label, None)] else: try: migration = executor.loader.get_migration_by_prefix(app_label, migration_name) except AmbiguityError: raise CommandError( "More than one migration matches '%s' in app '%s'. " "Please be more specific." % (migration_name, app_label) ) except KeyError: raise CommandError("Cannot find a migration matching '%s' from app '%s'." % ( migration_name, app_label)) targets = [(app_label, migration.name)] target_app_labels_only = False elif options['app_label']: app_label = options['app_label'] if app_label not in executor.loader.migrated_apps: raise CommandError( "App '%s' does not have migrations (you cannot selectively " "sync unmigrated apps)" % app_label ) targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label] else: targets = executor.loader.graph.leaf_nodes() run_syncdb = True plan = executor.migration_plan(targets) # Print some useful info if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:")) if run_syncdb and executor.loader.unmigrated_apps: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + (", ".join(executor.loader.unmigrated_apps)) ) if target_app_labels_only: self.stdout.write( self.style.MIGRATE_LABEL(" Apply all migrations: ") + (", ".join(set(a for a, n in targets)) or "(none)") ) else: if targets[0][1] is None: self.stdout.write(self.style.MIGRATE_LABEL( " Unapply all migrations: ") + "%s" % (targets[0][0], ) ) else: self.stdout.write(self.style.MIGRATE_LABEL( " Target specific migration: ") + "%s, from %s" % (targets[0][1], targets[0][0]) ) # Run the syncdb phase. # If you ever manage to get rid of this, I owe you many, many drinks. # Note that pre_migrate is called from inside here, as it needs # the list of models about to be installed. if run_syncdb and executor.loader.unmigrated_apps: if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:")) created_models = self.sync_apps(connection, executor.loader.unmigrated_apps) else: created_models = [] # The test runner requires us to flush after a syncdb but before migrations, # so do that here. if options.get("test_flush", False): call_command( 'flush', verbosity=max(self.verbosity - 1, 0), interactive=False, database=db, reset_sequences=False, inhibit_post_migrate=True, ) # Migrate! if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:")) if not plan: if self.verbosity >= 1: self.stdout.write(" No migrations to apply.") # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: self.stdout.write(self.style.NOTICE( " Your models have changes that are not yet reflected " "in a migration, and so won't be applied." )) self.stdout.write(self.style.NOTICE( " Run 'manage.py makemigrations' to make new " "migrations, and then re-run 'manage.py migrate' to " "apply them." )) else: executor.migrate(targets, plan, fake=options.get("fake", False)) # Send the post_migrate signal, so individual apps can do whatever they need # to do at this point. emit_post_migrate_signal(created_models, self.verbosity, self.interactive, connection.alias)
def test_create(self): """ Tests making a ProjectState from an Apps """ new_apps = Apps() class Author(models.Model): name = models.CharField(max_length=255) bio = models.TextField() age = models.IntegerField(blank=True, null=True) class Meta: app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] class AuthorProxy(Author): class Meta: app_label = "migrations" apps = new_apps proxy = True ordering = ["name"] class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author) contributors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps verbose_name = "tome" db_table = "test_tome" project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] author_proxy_state = project_state.models['migrations', 'authorproxy'] book_state = project_state.models['migrations', 'book'] self.assertEqual(author_state.app_label, "migrations") self.assertEqual(author_state.name, "Author") self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"]) self.assertEqual(author_state.fields[1][1].max_length, 255) self.assertEqual(author_state.fields[2][1].null, False) self.assertEqual(author_state.fields[3][1].null, True) self.assertEqual(author_state.options, {"unique_together": {("name", "bio")}}) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(book_state.app_label, "migrations") self.assertEqual(book_state.name, "Book") self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertEqual(book_state.fields[2][1].null, False) self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField") self.assertEqual(book_state.options, {"verbose_name": "tome", "db_table": "test_tome"}) self.assertEqual(book_state.bases, (models.Model, )) self.assertEqual(author_proxy_state.app_label, "migrations") self.assertEqual(author_proxy_state.name, "AuthorProxy") self.assertEqual(author_proxy_state.fields, []) self.assertEqual(author_proxy_state.options, {"proxy": True, "ordering": ["name"]}) self.assertEqual(author_proxy_state.bases, ("migrations.author", ))