def test_create_model(self): """ Tests the CreateModel operation. Most other tests use this operation as part of setup, so check failures here first. """ operation = migrations.CreateModel( "Pony", [ ("id", models.AutoField(primary_key=True)), ("pink", models.IntegerField(default=1)), ], ) # Test the state alteration project_state = ProjectState() new_state = project_state.clone() operation.state_forwards("test_crmo", new_state) self.assertEqual(new_state.models["test_crmo", "pony"].name, "Pony") self.assertEqual(len(new_state.models["test_crmo", "pony"].fields), 2) # Test the database alteration self.assertTableNotExists("test_crmo_pony") with connection.schema_editor() as editor: operation.database_forwards("test_crmo", editor, project_state, new_state) self.assertTableExists("test_crmo_pony") # And test reversal with connection.schema_editor() as editor: operation.database_backwards("test_crmo", editor, new_state, project_state) self.assertTableNotExists("test_crmo_pony") # And deconstruction definition = operation.deconstruct() self.assertEqual(definition[0], "CreateModel") self.assertEqual(len(definition[1]), 2) self.assertEqual(len(definition[2]), 0) self.assertEqual(definition[1][0], "Pony")
def test_manager_refer_correct_model_version(self): """ #24147 - Tests that managers refer to the correct version of a historical model """ project_state = ProjectState() project_state.add_model( ModelState( app_label="migrations", name="Tag", fields=[("id", models.AutoField(primary_key=True)), ("hidden", models.BooleanField())], managers=[("food_mgr", FoodManager("a", "b")), ("food_qs", FoodQuerySet.as_manager())], ) ) old_model = project_state.apps.get_model("migrations", "tag") new_state = project_state.clone() operation = RemoveField("tag", "hidden") operation.state_forwards("migrations", new_state) new_model = new_state.apps.get_model("migrations", "tag") self.assertIsNot(old_model, new_model) self.assertIs(old_model, old_model.food_mgr.model) self.assertIs(old_model, old_model.food_qs.model) self.assertIs(new_model, new_model.food_mgr.model) self.assertIs(new_model, new_model.food_qs.model) self.assertIsNot(old_model.food_mgr, new_model.food_mgr) self.assertIsNot(old_model.food_qs, new_model.food_qs) self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model) self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
def test_install_plugin(self): """ Test we can load the example plugin that every version of MySQL ships with. """ self.assertPluginNotExists("metadata_lock_info") state = ProjectState() operation = InstallPlugin("metadata_lock_info", "metadata_lock_info.so") self.assertEqual( operation.describe(), "Installs plugin metadata_lock_info from metadata_lock_info.so" ) new_state = state.clone() with connection.schema_editor() as editor: operation.database_forwards("django_mysql_tests", editor, state, new_state) self.assertPluginExists("metadata_lock_info") new_state = state.clone() with connection.schema_editor() as editor: operation.database_backwards("django_mysql_tests", editor, new_state, state) self.assertPluginNotExists("metadata_lock_info")
def test_create_model(self): """ Tests the CreateModel operation. """ operation = migrations.CreateModel( "uuidmodel", [ ("id", models.AutoField(primary_key=True)), ("uuid", UUIDField(auto=True)), ], ) # Test the state alteration project_state = ProjectState() new_state = project_state.clone() operation.state_forwards("test_crmo", new_state) self.assertEqual(new_state.models["test_crmo", "uuidmodel"].name, "uuidmodel") self.assertEqual(len(new_state.models["test_crmo", "uuidmodel"].fields), 2) # Test the database alteration self.assertTableNotExists("test_crmo_uuidmodel") with connection.schema_editor() as editor: operation.database_forwards("test_crmo", editor, project_state, new_state) self.assertTableExists("test_crmo_uuidmodel") self.assertColumnExists("test_crmo_uuidmodel", "uuid") # And test reversal with connection.schema_editor() as editor: operation.database_backwards("test_crmo", editor, new_state, project_state) self.assertTableNotExists("test_crmo_uuidmodel") # And deconstruction definition = operation.deconstruct() self.assertEqual(definition[0], "CreateModel") self.assertEqual(len(definition[1]), 2) self.assertEqual(len(definition[2]), 0) self.assertEqual(definition[1][0], "uuidmodel")
def test_ignore_order_wrt(self): """ Makes sure ProjectState doesn't include OrderWrt fields when making from existing models. """ new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) class Meta: app_label = "migrations" apps = new_apps order_with_respect_to = "author" # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Book)) self.assertEqual( [name for name, field in project_state.models["migrations", "book"].fields], ["id", "author"], )
def test_install_plugin(self): """ Test we can load the example plugin that every version of MySQL ships with. """ assert not plugin_exists("metadata_lock_info") state = ProjectState() operation = InstallPlugin("metadata_lock_info", "metadata_lock_info.so") assert ( operation.describe() == "Installs plugin metadata_lock_info from metadata_lock_info.so" ) new_state = state.clone() with connection.schema_editor() as editor: operation.database_forwards("testapp", editor, state, new_state) assert plugin_exists("metadata_lock_info") new_state = state.clone() with connection.schema_editor() as editor: operation.database_backwards("testapp", editor, new_state, state) assert not plugin_exists("metadata_lock_info")
def test_repr(self): field = models.CharField(max_length=1) state = ModelState('app', 'Model', [('name', field)], bases=['app.A', 'app.B', 'app.C']) self.assertEqual(repr(state), "<ModelState: 'app.Model'>") project_state = ProjectState() project_state.add_model(state) with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"): project_state.apps
def test_real_apps(self): """ Tests that including real apps can resolve dangling FK errors. This test relies on the fact that contenttypes is always loaded. """ new_apps = Apps() class TestModel(models.Model): ct = models.ForeignKey("contenttypes.ContentType") class Meta: app_label = "migrations" apps = new_apps # If we just stick it into an empty state it should fail project_state = ProjectState() project_state.add_model_state(ModelState.from_model(TestModel)) with self.assertRaises(ValueError): project_state.render() # If we include the real app it should succeed project_state = ProjectState(real_apps=["contenttypes"]) project_state.add_model_state(ModelState.from_model(TestModel)) rendered_state = project_state.render() self.assertEqual( len([x for x in rendered_state.get_models() if x._meta.app_label == "migrations"]), 1, )
def test_render(self): """ Tests rendering a ProjectState into an Apps. """ project_state = ProjectState() project_state.add_model_state(ModelState( app_label="migrations", name="Tag", fields=[ ("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100)), ("hidden", models.BooleanField()), ], )) project_state.add_model_state(ModelState( app_label="migrations", name="SubTag", fields=[ ('tag_ptr', models.OneToOneField( auto_created=True, primary_key=True, to_field='id', serialize=False, to='migrations.Tag', )), ("awesome", models.BooleanField()), ], bases=("migrations.Tag",), )) base_mgr = models.Manager() mgr1 = FoodManager('a', 'b') mgr2 = FoodManager('x', 'y', c=3, d=4) project_state.add_model_state(ModelState( app_label="migrations", name="Food", fields=[ ("id", models.AutoField(primary_key=True)), ], managers=[ # The ordering we really want is objects, mgr1, mgr2 ('default', base_mgr), ('food_mgr2', mgr2), ('food_mgr1', mgr1), ] )) new_apps = project_state.render() self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("name")[0].max_length, 100) self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("hidden")[0].null, False) self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2) Food = new_apps.get_model("migrations", "Food") managers = sorted(Food._meta.managers) self.assertEqual([mgr.name for _, mgr, _ in managers], ['default', 'food_mgr1', 'food_mgr2']) self.assertEqual([mgr.__class__ for _, mgr, _ in managers], [models.Manager, FoodManager, FoodManager]) self.assertIs(managers[0][1], Food._default_manager)
def test_create_model(self): operation = migrations.CreateModel("Pony", [ ('pony_id', models.AutoField(primary_key=True)), ('pink', models.IntegerField(default=1)), ]) project_state = ProjectState() new_state = project_state.clone() operation.state_forwards('tests', new_state) self.assertTableNotExists('tests_pony') with connection.schema_editor() as editor: operation.database_forwards('tests', editor, project_state, new_state) self.assertTableExists('tests_pony') with connection.schema_editor() as editor: operation.database_backwards('tests', editor, new_state, project_state) self.assertTableNotExists('tests_pony')
def test_self_relation(self): """ #24513 - Modifying an object pointing to itself would cause it to be rendered twice and thus breaking its related M2M through objects. """ class A(models.Model): to_a = models.ManyToManyField('something.A', symmetrical=False) class Meta: app_label = "something" def get_model_a(state): return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0] project_state = ProjectState() project_state.add_model((ModelState.from_model(A))) self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1) old_state = project_state.clone() operation = AlterField( model_name="a", name="to_a", field=models.ManyToManyField("something.A", symmetrical=False, blank=True) ) # At this point the model would be rendered twice causing its related # M2M through objects to point to an old copy and thus breaking their # attribute lookup. operation.state_forwards("something", project_state) model_a_old = get_model_a(old_state) model_a_new = get_model_a(project_state) self.assertIsNot(model_a_old, model_a_new) # Tests that the old model's _meta is still consistent field_to_a_old = model_a_old._meta.get_field("to_a") self.assertEqual(field_to_a_old.m2m_field_name(), "from_a") self.assertEqual(field_to_a_old.m2m_reverse_field_name(), "to_a") self.assertIs(field_to_a_old.related_model, model_a_old) self.assertIs(field_to_a_old.remote_field.through._meta.get_field('to_a').related_model, model_a_old) self.assertIs(field_to_a_old.remote_field.through._meta.get_field('from_a').related_model, model_a_old) # Tests that the new model's _meta is still consistent field_to_a_new = model_a_new._meta.get_field("to_a") self.assertEqual(field_to_a_new.m2m_field_name(), "from_a") self.assertEqual(field_to_a_new.m2m_reverse_field_name(), "to_a") self.assertIs(field_to_a_new.related_model, model_a_new) self.assertIs(field_to_a_new.remote_field.through._meta.get_field('to_a').related_model, model_a_new) self.assertIs(field_to_a_new.remote_field.through._meta.get_field('from_a').related_model, model_a_new)
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, **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 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_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 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 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_mysql_cache_migration(self): out = StringIO() call_command('mysql_cache_migration', stdout=out) output = out.getvalue() # Lint it with captured_stdout() as stderr: errors = check_code(output) self.assertEqual( errors, 0, "Encountered {} errors whilst trying to lint the mysql cache " "migration.\nMigration:\n\n{}\n\nLint errors:\n\n{}" .format(errors, output, stderr.getvalue()) ) # Dynamic import and check migration_mod = imp.new_module('0001_add_cache_tables') six.exec_(output, migration_mod.__dict__) self.assertTrue(hasattr(migration_mod, 'Migration')) migration = migration_mod.Migration self.assertTrue(hasattr(migration, 'dependencies')) self.assertTrue(hasattr(migration, 'operations')) # Since they all have the same table name, there should only be one # operation self.assertEqual(len(migration.operations), 1) # Now run the migration forwards and backwards to check it works operation = migration.operations[0] self.drop_table() self.assertTableNotExists(self.table_name) state = ProjectState() new_state = state.clone() with connection.schema_editor() as editor: operation.database_forwards("django_mysql_tests", editor, state, new_state) self.assertTableExists(self.table_name) new_state = state.clone() with connection.schema_editor() as editor: operation.database_backwards("django_mysql_tests", editor, new_state, state) self.assertTableNotExists(self.table_name) self.create_table()
def test_create(self): """ Tests making a ProjectState from an AppCache """ new_app_cache = BaseAppCache() 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" app_cache = new_app_cache unique_together = ["name", "bio"] class AuthorProxy(Author): class Meta: app_label = "migrations" app_cache = new_app_cache proxy = True ordering = ["name"] class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author) class Meta: app_label = "migrations" app_cache = new_app_cache verbose_name = "tome" db_table = "test_tome" project_state = ProjectState.from_app_cache(new_app_cache) 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": set(("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"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertEqual(book_state.fields[2][1].null, False) 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", ))
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_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_render_unique_app_labels(self): """ Tests that the ProjectState render method doesn't raise an ImproperlyConfigured exception about unique labels if two dotted app names have the same last part. """ class A(models.Model): class Meta: app_label = "django.contrib.auth" class B(models.Model): class Meta: app_label = "vendor.auth" # Make a ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) self.assertEqual(len(project_state.apps.get_models()), 2)
def test_create_model(self): """ Tests that CreateModel honours multi-db settings. """ operation = migrations.CreateModel( "Pony", [("id", models.AutoField(primary_key=True)), ("pink", models.IntegerField(default=1))] ) # Test the state alteration project_state = ProjectState() new_state = project_state.clone() operation.state_forwards("test_crmo", new_state) # Test the database alteration self.assertTableNotExists("test_crmo_pony") with connection.schema_editor() as editor: operation.database_forwards("test_crmo", editor, project_state, new_state) self.assertTableNotExists("test_crmo_pony") # And test reversal with connection.schema_editor() as editor: operation.database_backwards("test_crmo", editor, new_state, project_state) self.assertTableNotExists("test_crmo_pony")
def test_render(self): """ Tests rendering a ProjectState into an AppCache. """ project_state = ProjectState() project_state.add_model_state(ModelState( "migrations", "Tag", [ ("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100)), ("hidden", models.BooleanField()), ], {}, None, )) new_app_cache = project_state.render() self.assertEqual(new_app_cache.get_model("migrations", "Tag")._meta.get_field_by_name("name")[0].max_length, 100) self.assertEqual(new_app_cache.get_model("migrations", "Tag")._meta.get_field_by_name("hidden")[0].null, False)
def test_install_soname(self): """ Test we can load the 'metadata_lock_info' library. """ assert not plugin_exists("metadata_lock_info") state = ProjectState() operation = InstallSOName("metadata_lock_info.so") assert operation.describe() == "Installs library metadata_lock_info.so" new_state = state.clone() with connection.schema_editor() as editor: operation.database_forwards("testapp", editor, state, new_state) assert plugin_exists("metadata_lock_info") new_state = state.clone() with connection.schema_editor() as editor: operation.database_backwards("testapp", editor, new_state, state) assert not plugin_exists("metadata_lock_info")
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_remove_relations(self): """ #24225 - Tests that relations between models are updated while remaining the relations and references for models of an old state. """ new_apps = Apps() class A(models.Model): class Meta: app_label = "something" apps = new_apps class B(models.Model): to_a = models.ForeignKey(A, models.CASCADE) class Meta: app_label = "something" apps = new_apps def get_model_a(state): return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0] project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1) old_state = project_state.clone() operation = RemoveField("b", "to_a") operation.state_forwards("something", project_state) # Tests that model from old_state still has the relation model_a_old = get_model_a(old_state) model_a_new = get_model_a(project_state) self.assertIsNot(model_a_old, model_a_new) self.assertEqual(len(model_a_old._meta.related_objects), 1) self.assertEqual(len(model_a_new._meta.related_objects), 0) # Same test for deleted model project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) old_state = project_state.clone() operation = DeleteModel("b") operation.state_forwards("something", project_state) model_a_old = get_model_a(old_state) model_a_new = get_model_a(project_state) self.assertIsNot(model_a_old, model_a_new) self.assertEqual(len(model_a_old._meta.related_objects), 1) self.assertEqual(len(model_a_new._meta.related_objects), 0)
def test_migrations_apply_to_templates(self): template = SchemaTemplate.objects.create(name='a') operation = migrations.CreateModel("Pony", [ ('pony_id', models.AutoField(primary_key=True)), ('pink', models.IntegerField(default=1)), ]) project_state = ProjectState() new_state = project_state.clone() operation.state_forwards('tests', new_state) template.activate() self.assertFalse('tests_pony' in get_table_list()) with connection.schema_editor() as editor: operation.database_forwards('tests', editor, project_state, new_state) template.activate() self.assertTrue('tests_pony' in get_table_list()) with connection.schema_editor() as editor: operation.database_backwards('tests', editor, new_state, project_state) template.activate() self.assertFalse('tests_pony' in get_table_list())
def test_add_relations(self): """ #24573 - Adding relations to existing models should reload the referenced models too. """ new_apps = Apps() class A(models.Model): class Meta: app_label = 'something' apps = new_apps class B(A): class Meta: app_label = 'something' apps = new_apps class C(models.Model): class Meta: app_label = 'something' apps = new_apps project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(C)) project_state.apps # We need to work with rendered models old_state = project_state.clone() model_a_old = old_state.apps.get_model('something', 'A') model_b_old = old_state.apps.get_model('something', 'B') model_c_old = old_state.apps.get_model('something', 'C') # Check that the relations between the old models are correct self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old) operation = AddField('c', 'to_a', models.OneToOneField( 'something.A', models.CASCADE, related_name='from_c', )) operation.state_forwards('something', project_state) model_a_new = project_state.apps.get_model('something', 'A') model_b_new = project_state.apps.get_model('something', 'B') model_c_new = project_state.apps.get_model('something', 'C') # Check that all models have changed self.assertIsNot(model_a_old, model_a_new) self.assertIsNot(model_b_old, model_b_new) self.assertIsNot(model_c_old, model_c_new) # Check that the relations between the old models still hold self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old) # Check that the relations between the new models correct self.assertIs(model_a_new._meta.get_field('b').related_model, model_b_new) self.assertIs(model_b_new._meta.get_field('a_ptr').related_model, model_a_new) self.assertIs(model_a_new._meta.get_field('from_c').related_model, model_c_new) self.assertIs(model_c_new._meta.get_field('to_a').related_model, model_a_new)
def handle(self, *app_labels, **options): self.verbosity = int(options.get('verbosity')) self.interactive = options.get('interactive') # Make sure the app they asked for exists app_labels = set(app_labels) bad_app_labels = set() for app_label in app_labels: try: cache.get_app(app_label) except ImproperlyConfigured: 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). loader = MigrationLoader(connections[DEFAULT_DB_ALIAS]) # Detect changes autodetector = MigrationAutodetector( loader.graph.project_state(), ProjectState.from_app_cache(cache), 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: 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, migrations in changes.items(): self.stdout.write( self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n") for migration in migrations: # Describe the migration writer = MigrationWriter(migration) 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 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 with open(writer.path, "w") as fh: fh.write(writer.as_string())
def handle(self, *args, **options): # self 是「命令处理对象」 print('【django.core.management.commands.migrate.Command.handle】args:', args) #print('【django.core.management.commands.migrate.Command.handle】options:' ) #for k, v in options.items(): # print(f'\t{k:<22}{v}') database = options['database'] # 默认值是 'default' if not options['skip_checks']: # 此方法定义在父类 django.core.management.base.BaseCommand 中 self.check(databases=[database]) 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) # connections 是 django.db.utils.ConnectionHandler 类的实例,叫做「数据库连接处理对象」 # 此处调用其 __getitem__ 方法,返回的是「数据库包装对象」 # 以 MySQL 为例,其所属类定义在 django.db.backends.mysql.base 模块中 # 所以下面的 connection 是 django.db.backends.mysql.base.DatabaseWrapper 类的实例 connection = connections[database] print( '【django.core.management.commands.migrate.Command.handle】connection:', connection) # 此方法定义在实例的父类中,是空函数 connection.prepare_database() # 下面这十几行代码用于处理数据版本控制相关的事情 # 此 MigrationExecutor 类定义在 django.db.migrations.executor 模块中,其实例是「数据库版本迁移执行器」 # 实例化时提供两个参数,它们分别会被赋值给实例的同名属性: # connection 是「数据库包装对象」,这是位置参数 # process_callback 是可选参数,这里提供的是定义在当前类中的方法 executor = MigrationExecutor(connection, self.migration_progress_callback) # executor 是「数据库版本迁移执行器」 # executor.loader 是 django.db.migrations.loader.MigrationLoader 类的实例 # 该实例的 connection 属性值就是 self 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. run_syncdb = options['run_syncdb'] 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 run_syncdb: if app_label in executor.loader.migrated_apps: raise CommandError( "Can't use run_syncdb with app '%s' as it has migrations." % app_label) elif 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: # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例 # 叫做「数据库版本迁移执行器」 # executor.loader 是 django.db.migrations.loader.MigrationLoader 类的实例 # 此实例的 graph 属性值是 django.db.migrations.graph.MigrationGraph 类的实例 # 调用其 leaf_nodes 获得各个应用中最新版本的迁移文件名 # targets 是列表,里面是二元元组,类似这样: # [('admin', '0003_logentry_add_action_flag_choices'), ...] targets = executor.loader.graph.leaf_nodes() #print('【django.core.management.commands.migrate.Command.handle】targets:') #for i in targets: # print('\t', i) # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例 # 叫做「数据库版本迁移执行器」 plan = executor.migration_plan(targets) exit_dry = plan and options['check_unapplied'] 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) if exit_dry: sys.exit(1) return if exit_dry: sys.exit(1) # At this point, ignore run_syncdb if there aren't any apps to sync. 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: if options['app_label']: self.stdout.write( self.style.MIGRATE_LABEL( " Synchronize unmigrated app: %s" % app_label)) else: 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: ') + str(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:")) if options['app_label']: self.sync_apps(connection, [app_label]) else: 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'] # executor 是 django.db.migrations.executor.MigrationExecutor 类的实例,叫做「数据库版本迁移执行器」 # 参数: # targets 列表,里面是类似 ('auth', '0012_alter_user_first_name_max_length') 这样的元组 # plan 列表,里面是类似 (<Migration auth.0001_initial>, False) 这样的元组 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 test_dangling_references_throw_error(self): new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Publisher(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) publisher = models.ForeignKey(Publisher, models.CASCADE) class Meta: app_label = "migrations" apps = new_apps class Magazine(models.Model): authors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Publisher)) project_state.add_model(ModelState.from_model(Book)) project_state.add_model(ModelState.from_model(Magazine)) self.assertEqual(len(project_state.apps.get_models()), 4) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model(ModelState.from_model(Book)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: migrations.Book.author)\n" " migrations.publisher (referred to by fields: migrations.Book.publisher)" ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And another with ManyToManyField. project_state = ProjectState() project_state.add_model(ModelState.from_model(Magazine)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: " "migrations.Magazine.authors, migrations.Magazine_authors.author)") with self.assertRaisesMessage(ValueError, msg): project_state.apps # And now with multiple models and multiple fields. project_state.add_model(ModelState.from_model(Book)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: migrations.Book.author, " "migrations.Magazine.authors, migrations.Magazine_authors.author)\n" " migrations.publisher (referred to by fields: migrations.Book.publisher)" ) with self.assertRaisesMessage(ValueError, msg): project_state.apps
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) 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.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")}, "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.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", )) 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.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.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.assertEqual( [mgr.args for name, mgr in food_order_manager_state.managers], [('a', 'b', 1, 2), ('x', 'y', 3, 4)])
def set_up_test_model( self, app_label, second_model=False, third_model=False, index=False, multicol_index=False, related_model=False, mti_model=False, proxy_model=False, manager_model=False, unique_together=False, options=False, db_table=None, index_together=False, constraints=None, ): """Creates a test model state and database table.""" # Make the "current" state. model_options = { 'swappable': 'TEST_SWAP_MODEL', 'index_together': [['weight', 'pink']] if index_together else [], 'unique_together': [['pink', 'weight']] if unique_together else [], } if options: model_options['permissions'] = [('can_groom', 'Can groom')] if db_table: model_options['db_table'] = db_table operations = [ migrations.CreateModel( 'Pony', [ ('id', models.AutoField(primary_key=True)), ('pink', models.IntegerField(default=3)), ('weight', models.FloatField()), ], options=model_options, ) ] if index: operations.append( migrations.AddIndex( 'Pony', models.Index(fields=['pink'], name='pony_pink_idx'), )) if multicol_index: operations.append( migrations.AddIndex( 'Pony', models.Index(fields=['pink', 'weight'], name='pony_test_idx'), )) if constraints: for constraint in constraints: operations.append(migrations.AddConstraint('Pony', constraint)) if second_model: operations.append( migrations.CreateModel('Stable', [ ('id', models.AutoField(primary_key=True)), ])) if third_model: operations.append( migrations.CreateModel('Van', [ ('id', models.AutoField(primary_key=True)), ])) if related_model: operations.append( migrations.CreateModel( 'Rider', [('id', models.AutoField(primary_key=True)), ('pony', models.ForeignKey('Pony', models.CASCADE)), ('friend', models.ForeignKey('self', models.CASCADE))], )) if mti_model: operations.append( migrations.CreateModel( 'ShetlandPony', fields=[ ('pony_ptr', models.OneToOneField( 'Pony', models.CASCADE, auto_created=True, parent_link=True, primary_key=True, to_field='id', serialize=False, )), ('cuteness', models.IntegerField(default=1)), ], bases=['%s.Pony' % app_label], )) if proxy_model: operations.append( migrations.CreateModel( 'ProxyPony', fields=[], options={'proxy': True}, bases=['%s.Pony' % app_label], )) if manager_model: from .models import FoodManager, FoodQuerySet operations.append( migrations.CreateModel( 'Food', fields=[ ('id', models.AutoField(primary_key=True)), ], managers=[ ('food_qs', FoodQuerySet.as_manager()), ('food_mgr', FoodManager('a', 'b')), ('food_mgr_kwargs', FoodManager('x', 'y', 3, 4)), ])) return self.apply_operations(app_label, ProjectState(), operations)
def test_render_project_dependencies(self): """ Tests that the ProjectState render method correctly renders models to account for inter-model base dependencies. """ new_apps = Apps() class A(models.Model): class Meta: app_label = "migrations" apps = new_apps class B(A): class Meta: app_label = "migrations" apps = new_apps class C(B): class Meta: app_label = "migrations" apps = new_apps class D(A): class Meta: app_label = "migrations" apps = new_apps class E(B): class Meta: app_label = "migrations" apps = new_apps proxy = True class F(D): class Meta: app_label = "migrations" apps = new_apps proxy = True # Make a ProjectState and render it project_state = ProjectState() project_state.add_model_state(ModelState.from_model(A)) project_state.add_model_state(ModelState.from_model(B)) project_state.add_model_state(ModelState.from_model(C)) project_state.add_model_state(ModelState.from_model(D)) project_state.add_model_state(ModelState.from_model(E)) project_state.add_model_state(ModelState.from_model(F)) final_apps = project_state.render() self.assertEqual(len(final_apps.get_models()), 6) # Now make an invalid ProjectState and make sure it fails project_state = ProjectState() project_state.add_model_state(ModelState.from_model(A)) project_state.add_model_state(ModelState.from_model(B)) project_state.add_model_state(ModelState.from_model(C)) project_state.add_model_state(ModelState.from_model(F)) with self.assertRaises(InvalidBasesError): project_state.render()
def handle(self, *args, **options): # We're monkey patching the __exit__ method of DatabaseSchemaEditor because it reenables constraint checking # which we explicitly DO NOT want to do. The problem is that without patching this if multiple tables have # incorrect foreign key constraints you can't fix one at a time - you have to fix both simultaneously (which # Django doesn't support). Fun times. DatabaseSchemaEditor.__exit__ = BaseDatabaseSchemaEditor.__exit__ # I would MUCH prefer to do this by calling super().handle(), but we have to edit the "connection" object within # migrate to make this work. Instead, below is the code as copied from django.core.management.commands.migrate.handle() # aside from the handful of lines between rows 55 and 60. 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() #### The below five lines of code are the new additions in migrate_no_fk try: constraint_check = connection.disable_constraint_checking() except: connection.connection = connection.connect() constraint_check = connection.disable_constraint_checking() #### End additions! # 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. run_syncdb = options['run_syncdb'] 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 run_syncdb: if app_label in executor.loader.migrated_apps: raise CommandError("Can't use run_syncdb with app '%s' as it has migrations." % app_label) elif 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 # At this point, ignore run_syncdb if there aren't any apps to sync. 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: if options['app_label']: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated app: %s" % app_label) ) else: 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:")) if options['app_label']: self.sync_apps(connection, [app_label]) else: 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 make_project_state(self, model_states): "Shortcut to make ProjectStates from lists of predefined models" project_state = ProjectState() for model_state in model_states: project_state.add_model_state(model_state.clone()) return project_state
def migrate(self, ops, state=None): class Migration(migrations.Migration): operations = ops migration = Migration('name', 'schema_test_app') with connection.schema_editor() as schema_editor: return migration.apply(state or ProjectState.from_apps(self.apps), schema_editor)
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 (%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)
def test_create(self): """ Tests making a ProjectState from an AppCache """ new_app_cache = BaseAppCache() 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" app_cache = new_app_cache unique_together = ["name", "bio"] class AuthorProxy(Author): class Meta: app_label = "migrations" app_cache = new_app_cache proxy = True ordering = ["name"] class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author) class Meta: app_label = "migrations" app_cache = new_app_cache verbose_name = "tome" db_table = "test_tome" project_state = ProjectState.from_app_cache(new_app_cache) 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": set(("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"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertEqual(book_state.fields[2][1].null, False) 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", ))
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 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) db = options['database'] connection = connections[db] try: loader.check_consistent_migration_file(connection) except InconsistentMigrationFileHistory as e: self.stderr.write(str(e)) # 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 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) if check_changes: sys.exit(1)
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'] 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['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(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])) 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_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 test_dangling_references_throw_error(self): new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author) class Meta: app_label = "migrations" apps = new_apps class Magazine(models.Model): authors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model_state(ModelState.from_model(Author)) project_state.add_model_state(ModelState.from_model(Book)) project_state.add_model_state(ModelState.from_model(Magazine)) rendered_state = project_state.render() self.assertEqual(len(rendered_state.get_models()), 3) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model_state(ModelState.from_model(Book)) with self.assertRaises(ValueError): rendered_state = project_state.render() # and another with ManyToManyField project_state = ProjectState() project_state.add_model_state(ModelState.from_model(Magazine)) with self.assertRaises(ValueError): rendered_state = project_state.render()
def test_render(self): """ Tests rendering a ProjectState into an Apps. """ project_state = ProjectState() project_state.add_model( ModelState( app_label="migrations", name="Tag", fields=[ ("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100)), ("hidden", models.BooleanField()), ], )) project_state.add_model( ModelState( app_label="migrations", name="SubTag", fields=[ ('tag_ptr', models.OneToOneField( auto_created=True, primary_key=True, to_field='id', serialize=False, to='migrations.Tag', )), ("awesome", models.BooleanField()), ], bases=("migrations.Tag", ), )) base_mgr = models.Manager() mgr1 = FoodManager('a', 'b') mgr2 = FoodManager('x', 'y', c=3, d=4) project_state.add_model( ModelState( app_label="migrations", name="Food", fields=[ ("id", models.AutoField(primary_key=True)), ], managers=[ # The ordering we really want is objects, mgr1, mgr2 ('default', base_mgr), ('food_mgr2', mgr2), ('food_mgr1', mgr1), ])) new_apps = project_state.apps self.assertEqual( new_apps.get_model("migrations", "Tag")._meta.get_field("name").max_length, 100) self.assertEqual( new_apps.get_model("migrations", "Tag")._meta.get_field("hidden").null, False) self.assertEqual( len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2) Food = new_apps.get_model("migrations", "Food") managers = sorted(Food._meta.managers) self.assertEqual([mgr.name for _, mgr, _ in managers], ['default', 'food_mgr1', 'food_mgr2']) self.assertEqual([mgr.__class__ for _, mgr, _ in managers], [models.Manager, FoodManager, FoodManager]) self.assertIs(managers[0][1], Food._default_manager)
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): database = options['database'] if not options['skip_checks']: self.check(databases=[database]) 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 connection = connections[database] # 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. run_syncdb = options['run_syncdb'] 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 run_syncdb: if app_label in executor.loader.migrated_apps: raise CommandError( "Can't use run_syncdb with app '%s' as it has migrations." % app_label) elif 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)) target = (app_label, migration.name) # Partially applied squashed migrations are not included in the # graph, use the last replacement instead. if (target not in executor.loader.graph.nodes and target in executor.loader.replacements): incomplete_migration = executor.loader.replacements[target] target = incomplete_migration.replaces[-1] targets = [target] 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() if options['prune']: if not options['app_label']: raise CommandError( 'Migrations can be pruned only when an app is specified.') if self.verbosity > 0: self.stdout.write('Pruning migrations:', self.style.MIGRATE_HEADING) to_prune = set(executor.loader.applied_migrations) - set( executor.loader.disk_migrations) squashed_migrations_with_deleted_replaced_migrations = [ migration_key for migration_key, migration_obj in executor.loader.replacements.items() if any(replaced in to_prune for replaced in migration_obj.replaces) ] if squashed_migrations_with_deleted_replaced_migrations: self.stdout.write( self.style.NOTICE( " Cannot use --prune because the following squashed " "migrations have their 'replaces' attributes and may not " "be recorded as applied:")) for migration in squashed_migrations_with_deleted_replaced_migrations: app, name = migration self.stdout.write(f' {app}.{name}') self.stdout.write( self.style.NOTICE( " Re-run 'manage.py migrate' if they are not marked as " "applied, and remove 'replaces' attributes in their " "Migration classes.")) else: to_prune = sorted(migration for migration in to_prune if migration[0] == app_label) if to_prune: for migration in to_prune: app, name = migration if self.verbosity > 0: self.stdout.write(self.style.MIGRATE_LABEL( f' Pruning {app}.{name}'), ending='') executor.recorder.record_unapplied(app, name) if self.verbosity > 0: self.stdout.write(self.style.SUCCESS(' OK')) elif self.verbosity > 0: self.stdout.write(' No migrations to prune.') plan = executor.migration_plan(targets) exit_dry = plan and options['check_unapplied'] 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) if exit_dry: sys.exit(1) return if exit_dry: sys.exit(1) if options['prune']: return # At this point, ignore run_syncdb if there aren't any apps to sync. 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: if options['app_label']: self.stdout.write( self.style.MIGRATE_LABEL( " Synchronize unmigrated app: %s" % app_label)) else: 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: ') + str(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, stdout=self.stdout, 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:")) if options['app_label']: self.sync_apps(connection, [app_label]) else: 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 in app(s): %s have changes that are not " "yet reflected in a migration, and so won't be " "applied." % ", ".join(repr(app) for app in sorted(changes)))) 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, stdout=self.stdout, 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) # 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 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) 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'] sub_author_state = project_state.models['migrations', 'subauthor'] 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": set([("name", "bio")]), "index_together": set([("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.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", )) 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", ))
def test_dangling_references_throw_error(self): new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Publisher(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) publisher = models.ForeignKey(Publisher, models.CASCADE) class Meta: app_label = "migrations" apps = new_apps class Magazine(models.Model): authors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Publisher)) project_state.add_model(ModelState.from_model(Book)) project_state.add_model(ModelState.from_model(Magazine)) self.assertEqual(len(project_state.apps.get_models()), 4) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model(ModelState.from_model(Book)) msg = ( "The field migrations.Book.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Book.publisher was declared with a lazy reference " "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And another with ManyToManyField. project_state = ProjectState() project_state.add_model(ModelState.from_model(Magazine)) msg = ( "The field migrations.Magazine.authors was declared with a lazy reference " "to 'migrations.author\', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Magazine_authors.author was declared with a lazy reference " "to \'migrations.author\', but app 'migrations' doesn't provide model 'author'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And now with multiple models and multiple fields. project_state.add_model(ModelState.from_model(Book)) msg = ( "The field migrations.Book.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Book.publisher was declared with a lazy reference " "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'.\n" "The field migrations.Magazine.authors was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Magazine_authors.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps
def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False): """ Creates a test model state and database table. """ # Delete the tables if they already exist with connection.cursor() as cursor: # Start with ManyToMany tables try: cursor.execute("DROP TABLE %s_pony_stables" % app_label) except DatabaseError: pass try: cursor.execute("DROP TABLE %s_pony_vans" % app_label) except DatabaseError: pass # Then standard model tables try: cursor.execute("DROP TABLE %s_pony" % app_label) except DatabaseError: pass try: cursor.execute("DROP TABLE %s_stable" % app_label) except DatabaseError: pass try: cursor.execute("DROP TABLE %s_van" % app_label) except DatabaseError: pass # Make the "current" state operations = [migrations.CreateModel( "Pony", [ ("id", models.AutoField(primary_key=True)), ("pink", models.IntegerField(default=3)), ("weight", models.FloatField()), ], )] if second_model: operations.append(migrations.CreateModel( "Stable", [ ("id", models.AutoField(primary_key=True)), ] )) if third_model: operations.append(migrations.CreateModel( "Van", [ ("id", models.AutoField(primary_key=True)), ] )) if related_model: operations.append(migrations.CreateModel( "Rider", [ ("id", models.AutoField(primary_key=True)), ("pony", models.ForeignKey("Pony")), ], )) if mti_model: operations.append(migrations.CreateModel( "ShetlandPony", fields=[ ('pony_ptr', models.OneToOneField( auto_created=True, primary_key=True, to_field='id', serialize=False, to='Pony', )), ("cuteness", models.IntegerField(default=1)), ], bases=['%s.Pony' % app_label], )) return self.apply_operations(app_label, ProjectState(), operations)
def handle(self, *args, **options): self.verbosity = int(options.get('verbosity')) self.interactive = options.get('interactive') self.show_traceback = options.get('traceback') self.load_initial_data = options.get('load_initial_data') self.test_database = options.get('test_database', False) # 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, args) # 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 len(args) > 2: raise CommandError( "Too many command-line arguments (expecting 'app_label' or 'app_label migrationname')" ) elif len(args) == 2: app_label, migration_name = args 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." % (app_label, migration_name)) except KeyError: raise CommandError( "Cannot find a migration matching '%s' from app '%s'." % (app_label, migration_name)) targets = [(app_label, migration.name)] target_app_labels_only = False elif len(args) == 1: app_label = args[0] 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: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + (", ".join(executor.loader.unmigrated_apps) or "(none)")) 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: 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 = [] # 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 needed.") # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.graph.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 handle(self, *app_labels, **options): self.written_files = [] 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"] self.scriptable = options["scriptable"] # If logs and prompts are diverted to stderr, remove the ERROR style. if self.scriptable: self.stderr.style_func = None # 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()): try: loader.check_consistent_history(connection) except OperationalError as error: warnings.warn( "Got an error checking a consistent migration history " "performed for database connection '%s': %s" % (alias, error), RuntimeWarning, ) # 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.log("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, prompt_output=self.log_output, ) else: questioner = NonInteractiveMigrationQuestioner( specified_apps=app_labels, dry_run=self.dry_run, verbosity=self.verbosity, log=self.log, ) # 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.log("No changes detected in app '%s'" % app_labels.pop()) else: self.log("No changes detected in apps '%s'" % ("', '".join(app_labels))) else: self.log("No changes detected") else: self.write_migration_files(changes) if check_changes: sys.exit(1)
def test_add_relations(self): """ #24573 - Adding relations to existing models should reload the referenced models too. """ new_apps = Apps() class A(models.Model): class Meta: app_label = 'something' apps = new_apps class B(A): class Meta: app_label = 'something' apps = new_apps class C(models.Model): class Meta: app_label = 'something' apps = new_apps project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(C)) project_state.apps # We need to work with rendered models old_state = project_state.clone() model_a_old = old_state.apps.get_model('something', 'A') model_b_old = old_state.apps.get_model('something', 'B') model_c_old = old_state.apps.get_model('something', 'C') # Check that the relations between the old models are correct self.assertIs( model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs( model_b_old._meta.get_field('a_ptr').related_model, model_a_old) operation = AddField( 'c', 'to_a', models.OneToOneField('something.A', related_name='from_c')) operation.state_forwards('something', project_state) model_a_new = project_state.apps.get_model('something', 'A') model_b_new = project_state.apps.get_model('something', 'B') model_c_new = project_state.apps.get_model('something', 'C') # Check that all models have changed self.assertIsNot(model_a_old, model_a_new) self.assertIsNot(model_b_old, model_b_new) self.assertIsNot(model_c_old, model_c_new) # Check that the relations between the old models still hold self.assertIs( model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs( model_b_old._meta.get_field('a_ptr').related_model, model_a_old) # Check that the relations between the new models correct self.assertIs( model_a_new._meta.get_field('b').related_model, model_b_new) self.assertIs( model_b_new._meta.get_field('a_ptr').related_model, model_a_new) self.assertIs( model_a_new._meta.get_field('from_c').related_model, model_c_new) self.assertIs( model_c_new._meta.get_field('to_a').related_model, model_a_new)
def test_runpython_manager_methods(self): def forwards(apps, schema_editor): UserModel = apps.get_model("auth", "User") user = UserModel.objects.create_user("user1", password="******") self.assertIsInstance(user, UserModel) operation = migrations.RunPython(forwards, migrations.RunPython.noop) project_state = ProjectState() project_state.add_model(ModelState.from_model(User)) project_state.add_model(ModelState.from_model(Group)) project_state.add_model(ModelState.from_model(Permission)) project_state.add_model(ModelState.from_model(ContentType)) new_state = project_state.clone() with connection.schema_editor() as editor: operation.state_forwards("test_manager_methods", new_state) operation.database_forwards( "test_manager_methods", editor, project_state, new_state, ) user = User.objects.get(username="******") self.assertTrue(user.check_password("secure"))
def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False, proxy_model=False, unique_together=False, options=False, db_table=None, index_together=False): """ Creates a test model state and database table. """ # Delete the tables if they already exist table_names = [ # Start with ManyToMany tables '_pony_stables', '_pony_vans', # Then standard model tables '_pony', '_stable', '_van', ] tables = [(app_label + table_name) for table_name in table_names] with connection.cursor() as cursor: table_names = connection.introspection.table_names(cursor) connection.disable_constraint_checking() sql_delete_table = connection.schema_editor().sql_delete_table with transaction.atomic(): for table in tables: if table in table_names: cursor.execute( sql_delete_table % { "table": connection.ops.quote_name(table), }) connection.enable_constraint_checking() # Make the "current" state model_options = { "swappable": "TEST_SWAP_MODEL", "index_together": [["weight", "pink"]] if index_together else [], "unique_together": [["pink", "weight"]] if unique_together else [], } if options: model_options["permissions"] = [("can_groom", "Can groom")] if db_table: model_options["db_table"] = db_table operations = [ migrations.CreateModel( "Pony", [ ("id", models.AutoField(primary_key=True)), ("pink", models.IntegerField(default=3)), ("weight", models.FloatField()), ], options=model_options, ) ] if second_model: operations.append( migrations.CreateModel("Stable", [ ("id", models.AutoField(primary_key=True)), ])) if third_model: operations.append( migrations.CreateModel("Van", [ ("id", models.AutoField(primary_key=True)), ])) if related_model: operations.append( migrations.CreateModel( "Rider", [("id", models.AutoField(primary_key=True)), ("pony", models.ForeignKey("Pony")), ("friend", models.ForeignKey("self"))], )) if mti_model: operations.append( migrations.CreateModel( "ShetlandPony", fields=[ ('pony_ptr', models.OneToOneField( auto_created=True, primary_key=True, to_field='id', serialize=False, to='Pony', )), ("cuteness", models.IntegerField(default=1)), ], bases=['%s.Pony' % app_label], )) if proxy_model: operations.append( migrations.CreateModel( "ProxyPony", fields=[], options={"proxy": True}, bases=['%s.Pony' % app_label], )) return self.apply_operations(app_label, ProjectState(), operations)
def handle(self, *app_labels, **options): self.verbosity = 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, 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 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, *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!"))