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", models.CASCADE) 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(ModelState.from_model(TestModel)) with self.assertRaises(ValueError): project_state.apps # If we include the real app it should succeed project_state = ProjectState(real_apps=["contenttypes"]) project_state.add_model(ModelState.from_model(TestModel)) rendered_state = project_state.apps self.assertEqual( len([x for x in rendered_state.get_models() if x._meta.app_label == "migrations"]), 1, )
def get_related_model_states(cls, model_state): model_states = {} for _name, field in model_state.fields: related_model_reference = get_remote_field_model(field) if related_model_reference: related_model = cls._meta.apps.get_model( related_model_reference) if issubclass(related_model, MutableModel): related_model_state = related_model.get_model_state( exclude_rels=True) else: related_model_state = ModelState.from_model( related_model, exclude_rels=True) model_states[related_model_state.app_label, related_model_state.name] = related_model_state for base in related_model_state.bases: if isinstance(base, string_types): base_model = cls._meta.apps.get_model(base) if issubclass(base_model, MutableModel): base_model_state = base_model.get_model_state( exclude_rels=True) else: base_model_state = ModelState.from_model( base_model, exclude_rels=True) model_states[base_model_state.app_label, base_model_state.name] = base_model_state return list(model_states.values())
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_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) 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_state(ModelState.from_model(Author)) project_state.add_model_state(ModelState.from_model(Book)) self.assertEqual( [ name for name, field in project_state.models["migrations", "book"].fields ], ["id", "author"], )
def test_add_relations(self): """ #24573 - Adding relations to existing models should reload the referenced models too. """ class A(models.Model): class Meta: app_label = 'something' class B(A): class Meta: app_label = 'something' class C(models.Model): class Meta: app_label = 'something' 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_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_dangling_references_throw_error(self): class Author(models.Model): name = models.TextField() class Book(models.Model): author = models.ForeignKey(Author) class Magazine(models.Model): authors = models.ManyToManyField(Author) # 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_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 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(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(C)) project_state.add_model(ModelState.from_model(D)) project_state.add_model(ModelState.from_model(E)) project_state.add_model(ModelState.from_model(F)) final_apps = project_state.apps 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(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(C)) project_state.add_model(ModelState.from_model(F)) with self.assertRaises(InvalidBasesError): project_state.apps
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 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_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_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_create_swappable(self): """ Tests making a ProjectState from an Apps with a swappable model """ 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 swappable = 'TEST_SWAPPABLE_MODEL' author_state = ModelState.from_model(Author) 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, {'swappable': 'TEST_SWAPPABLE_MODEL'}) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(author_state.managers, [])
def migrate(): """Migrate the database. This is a simplified version of Django's ``migrate`` management command that does not require ``management.call_command`` which is not functional in the native binary environment. """ # Import the 'management' module within each installed app, to register # dispatcher events. verbosity = int(settings.DEBUG) interactive = False for app_config in apps.get_app_configs(): if module_has_submodule(app_config.module, "management"): import_module(".management", app_config.name) connection = connections[DEFAULT_DB_ALIAS] connection.prepare_database() executor = MigrationExecutor(connection, migration_progress_callback) executor.loader.check_consistent_history(connection) conflicts = executor.loader.detect_conflicts() if conflicts: raise Exception("Conflicting mgirations detected.") targets = executor.loader.graph.leaf_nodes() plan = executor.migration_plan(targets) pre_migrate_state = executor._create_project_state( with_applied_migrations=True) pre_migrate_apps = pre_migrate_state.apps emit_pre_migrate_signal( verbosity, False, connection.alias, apps=pre_migrate_apps, plan=plan, ) post_migrate_state = executor.migrate( targets, plan=plan, state=pre_migrate_state.clone(), fake=False, fake_initial=False, ) post_migrate_state.clear_delayed_apps_cache() post_migrate_apps = post_migrate_state.apps 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 ]) emit_post_migrate_signal( verbosity, interactive, connection.alias, apps=post_migrate_apps, plan=plan, ) logger.info("Database configured.")
def test_create_swappable(self): """ Tests making a ProjectState from an Apps with a swappable model """ 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 swappable = 'TEST_SWAPPABLE_MODEL' author_state = ModelState.from_model(Author) self.assertEqual(author_state.app_label, 'migrations') self.assertEqual(author_state.name, 'Author') self.assertEqual([x for x, y in author_state.fields], ['id', 'name', 'bio', 'age']) self.assertEqual(author_state.fields[1][1].max_length, 255) self.assertIs(author_state.fields[2][1].null, False) self.assertIs(author_state.fields[3][1].null, True) self.assertEqual(author_state.options, {'swappable': 'TEST_SWAPPABLE_MODEL'}) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(author_state.managers, [])
def finalize_migrations(post_migrate_state): """Finalize any migrations operations. This will update any internal state in Django for any migrations that were applied and represented by the provided post-migrate state. Args: post_migrate_state (object): The state generated from applying migrations. This must be the result of :py:meth:`apply_migrations`. """ assert supports_migrations, \ 'This cannot be called on Django 1.6 or earlier.' if django_version >= (1, 10): # On Django 1.10, we have a few more steps for generating the state # needed for the signal. if django_version >= (1, 11): post_migrate_state.clear_delayed_apps_cache() post_migrate_apps = post_migrate_state.apps assert post_migrate_apps is not None model_keys = [] with post_migrate_apps.bulk_update(): 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(get_model(*model)) for model in model_keys ])
def test_index_together_migration(self): class IndexTogetherModel2(TranslatableModel): sfield_a = models.CharField(max_length=250) sfield_b = models.CharField(max_length=250) translations = TranslatedFields( tfield_a = models.CharField(max_length=250), tfield_b = models.CharField(max_length=250), ) class Meta: index_together = [('sfield_a', 'sfield_b'), ('tfield_a', 'tfield_b')] from django.db.migrations.state import ModelState state = ModelState.from_model(IndexTogetherModel2) self.assertEqual(state.options['index_together'], {('sfield_a', 'sfield_b')}) state = ModelState.from_model(IndexTogetherModel2._meta.translations_model) self.assertEqual(state.options['index_together'], {('tfield_a', 'tfield_b')})
def test_render_model_with_multiple_inheritance(self): class Foo(models.Model): class Meta: app_label = "migrations" app_cache = AppCache() class Bar(models.Model): class Meta: app_label = "migrations" app_cache = AppCache() class FooBar(Foo, Bar): class Meta: app_label = "migrations" app_cache = AppCache() app_cache = AppCache() # We shouldn't be able to render yet ms = ModelState.from_model(FooBar) with self.assertRaises(InvalidBasesError): ms.render(app_cache) # Once the parent models are in the app cache, it should be fine ModelState.from_model(Foo).render(app_cache) ModelState.from_model(Bar).render(app_cache) ModelState.from_model(FooBar).render(app_cache)
def test_render_model_with_multiple_inheritance(self): class Foo(models.Model): class Meta: app_label = "migrations" apps = Apps() class Bar(models.Model): class Meta: app_label = "migrations" apps = Apps() class FooBar(Foo, Bar): class Meta: app_label = "migrations" apps = Apps() apps = Apps(["migrations"]) # We shouldn't be able to render yet ms = ModelState.from_model(FooBar) with self.assertRaises(InvalidBasesError): ms.render(apps) # Once the parent models are in the app registry, it should be fine ModelState.from_model(Foo).render(apps) ModelState.from_model(Bar).render(apps) ModelState.from_model(FooBar).render(apps)
def test_render_multiple_inheritance(self): # Use a custom app cache to avoid polluting the global one. new_app_cache = BaseAppCache() class Book(models.Model): title = models.CharField(max_length=1000) class Meta: app_label = "migrations" app_cache = new_app_cache class Novel(Book): class Meta: app_label = "migrations" app_cache = new_app_cache yet_another_app_cache = BaseAppCache() ModelState.from_model(Novel).render(yet_another_app_cache)
def create_gm2m_intermediary_model(field, klass): """ Creates a generic M2M model for the GM2M field 'field' on model 'klass' """ from django.db import models managed = klass._meta.managed name = "%s_%s" % (klass._meta.object_name, field.name) model_name = klass._meta.model_name db_table = db_backends_utils.truncate_name( "%s_%s" % (klass._meta.db_table, field.name), connection.ops.max_name_length() ) meta_kwargs = { "db_table": db_table, "managed": managed, "auto_created": klass, "app_label": klass._meta.app_label, "db_tablespace": klass._meta.db_tablespace, "unique_together": (SRC_ATTNAME, CT_ATTNAME, FK_ATTNAME), "verbose_name": "%s-generic relationship" % model_name, "verbose_name_plural": "%s-generic relationships" % model_name, "apps": field.model._meta.apps, } meta = type("Meta", (object,), meta_kwargs) fk_maxlength = 16 # default value if field.pk_maxlength is not False: fk_maxlength = field.pk_maxlength model = type( str(name), (models.Model,), { "Meta": meta, "__module__": klass.__module__, SRC_ATTNAME: models.ForeignKey( klass, on_delete=field.rel.on_delete_src, db_constraint=field.rel.db_constraint ), CT_ATTNAME: models.ForeignKey(ct.ContentType, db_constraint=field.rel.db_constraint), FK_ATTNAME: models.CharField(max_length=fk_maxlength), TGT_ATTNAME: ct.GenericForeignKey( ct_field=CT_ATTNAME, fk_field=FK_ATTNAME, for_concrete_model=field.rel.for_concrete_model ), }, ) if is_fake_model(klass): # if we are building a fake model for migrations purposes, create a # ModelState from the model and render it (see issues #3 and #5) return ModelState.from_model(model).render(klass._meta.apps) return model
def test_unique_together_migration(self): class UniqueTogetherModel3(TranslatableModel): sfield_a = models.CharField(max_length=250) sfield_b = models.CharField(max_length=250) translations = TranslatedFields( tfield_a = models.CharField(max_length=250), tfield_b = models.CharField(max_length=250), ) class Meta: unique_together = [('sfield_a', 'sfield_b'), ('tfield_a', 'tfield_b')] from django.db.migrations.state import ModelState state = ModelState.from_model(UniqueTogetherModel3) self.assertEqual(state.options['unique_together'], {('sfield_a', 'sfield_b')}) state = ModelState.from_model(UniqueTogetherModel3._meta.translations_model) self.assertEqual(state.options['unique_together'], {('language_code', 'master'), ('tfield_a', 'tfield_b')})
def create_gm2m_intermediary_model(field, klass): """ Creates a generic M2M model for the GM2M field 'field' on model 'klass' """ from django.db import models managed = klass._meta.managed name = '%s_%s' % (klass._meta.object_name, field.name) model_name = klass._meta.model_name db_table = db_backends_utils.truncate_name( '%s_%s' % (klass._meta.db_table, field.name), connection.ops.max_name_length()) meta_kwargs = { 'db_table': db_table, 'managed': managed, 'auto_created': klass, 'app_label': klass._meta.app_label, 'db_tablespace': klass._meta.db_tablespace, 'unique_together': (SRC_ATTNAME, CT_ATTNAME, FK_ATTNAME), 'verbose_name': '%s-generic relationship' % model_name, 'verbose_name_plural': '%s-generic relationships' % model_name, 'apps': field.model._meta.apps, } meta = type('Meta', (object,), meta_kwargs) fk_maxlength = 16 # default value if field.pk_maxlength is not False: fk_maxlength = field.pk_maxlength model = type(str(name), (models.Model,), { 'Meta': meta, '__module__': klass.__module__, SRC_ATTNAME: models.ForeignKey(klass, auto_created=True, on_delete=field.remote_field.on_delete_src, db_constraint=field.remote_field.db_constraint), CT_ATTNAME: models.ForeignKey(ct.ContentType, db_constraint=field.remote_field.db_constraint), FK_ATTNAME: models.CharField(max_length=fk_maxlength), TGT_ATTNAME: ct.GenericForeignKey( ct_field=CT_ATTNAME, fk_field=FK_ATTNAME, for_concrete_model=field.remote_field.for_concrete_model, ), }) if is_fake_model(klass): # if we are building a fake model for migrations purposes, create a # ModelState from the model and render it (see issues #3 and #5) return ModelState.from_model(model).render(klass._meta.apps) return model
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_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_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(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Book)) project_state.add_model(ModelState.from_model(Magazine)) self.assertEqual(len(project_state.apps.get_models()), 3) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model(ModelState.from_model(Book)) with self.assertRaises(ValueError): project_state.apps # and another with ManyToManyField project_state = ProjectState() project_state.add_model(ModelState.from_model(Magazine)) with self.assertRaises(ValueError): project_state.apps
def test_order_with_respect_to_private_field(self): class PrivateFieldModel(models.Model): content_type = models.ForeignKey('contenttypes.ContentType', models.CASCADE) object_id = models.PositiveIntegerField() private = GenericForeignKey() class Meta: order_with_respect_to = 'private' state = ModelState.from_model(PrivateFieldModel) self.assertNotIn('order_with_respect_to', state.options)
def create_model(entity): model = dynamic_models[entity.name] model_state = ModelState.from_model(model) ops.append(CreateModel( name=model_state.name, fields=model_state.fields, options=model_state.options, bases=model_state.bases, managers=model_state.managers )) created_models.add(entity.name)
def get_related_model_states(cls, model_state): model_states = {} for _name, field in model_state.fields: related_model_reference = get_remote_field_model(field) if related_model_reference: related_model = cls._meta.apps.get_model(related_model_reference) if issubclass(related_model, MutableModel): related_model_state = related_model.get_model_state(exclude_rels=True) else: related_model_state = ModelState.from_model(related_model, exclude_rels=True) model_states[related_model_state.app_label, related_model_state.name] = related_model_state for base in related_model_state.bases: if isinstance(base, string_types): base_model = cls._meta.apps.get_model(base) if issubclass(base_model, MutableModel): base_model_state = base_model.get_model_state(exclude_rels=True) else: base_model_state = ModelState.from_model(base_model, exclude_rels=True) model_states[base_model_state.app_label, base_model_state.name] = base_model_state return list(model_states.values())
def test_render_multiple_inheritance(self): # Use a custom app cache to avoid polluting the global one. new_app_cache = BaseAppCache() class Book(models.Model): title = models.CharField(max_length=1000) class Meta: app_label = "migrations" app_cache = new_app_cache class Novel(Book): class Meta: app_label = "migrations" app_cache = new_app_cache # First, test rendering individually yet_another_app_cache = BaseAppCache() # We shouldn't be able to render yet with self.assertRaises(ValueError): ModelState.from_model(Novel).render(yet_another_app_cache) # Once the parent model is in the app cache, it should be fine ModelState.from_model(Book).render(yet_another_app_cache) ModelState.from_model(Novel).render(yet_another_app_cache)
def test_index_together_migration(self): class IndexTogetherModel2(TranslatableModel): sfield_a = models.CharField(max_length=250) sfield_b = models.CharField(max_length=250) translations = TranslatedFields( tfield_a=models.CharField(max_length=250), tfield_b=models.CharField(max_length=250), ) class Meta: index_together = [('sfield_a', 'sfield_b'), ('tfield_a', 'tfield_b')] from django.db.migrations.state import ModelState state = ModelState.from_model(IndexTogetherModel2) self.assertEqual(state.options['index_together'], {('sfield_a', 'sfield_b')}) state = ModelState.from_model( IndexTogetherModel2._meta.translations_model) self.assertEqual(state.options['index_together'], {('tfield_a', 'tfield_b')})
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 test_unique_together_migration(self): class UniqueTogetherModel3(TranslatableModel): sfield_a = models.CharField(max_length=250) sfield_b = models.CharField(max_length=250) translations = TranslatedFields( tfield_a=models.CharField(max_length=250), tfield_b=models.CharField(max_length=250), ) class Meta: unique_together = [('sfield_a', 'sfield_b'), ('tfield_a', 'tfield_b')] from django.db.migrations.state import ModelState state = ModelState.from_model(UniqueTogetherModel3) self.assertEqual(state.options['unique_together'], {('sfield_a', 'sfield_b')}) state = ModelState.from_model( UniqueTogetherModel3._meta.translations_model) self.assertEqual(state.options['unique_together'], {('language_code', 'master'), ('tfield_a', 'tfield_b')})
def test_render_model_inheritance(self): class Book(models.Model): title = models.CharField(max_length=1000) class Meta: app_label = "migrations" apps = Apps() class Novel(Book): class Meta: app_label = "migrations" apps = Apps() # First, test rendering individually apps = Apps(["migrations"]) # We shouldn't be able to render yet ms = ModelState.from_model(Novel) with self.assertRaises(InvalidBasesError): ms.render(apps) # Once the parent model is in the app registry, it should be fine ModelState.from_model(Book).render(apps) ModelState.from_model(Novel).render(apps)
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 get_create_sql_for_model(model): model_state = ModelState.from_model(model) # Create a fake migration with the CreateModel operation cm = operations.CreateModel(name=model_state.name, fields=model_state.fields) migration = Migration("fake_migration", "app") migration.operations.append(cm) # Let the migration framework think that the project is in an initial state state = ProjectState() # Get the SQL through the schema_editor bound to the connection connection = connections['default'] with connection.schema_editor(collect_sql=True, atomic=migration.atomic) as schema_editor: state = migration.apply(state, schema_editor, collect_sql=True) # return the CREATE TABLE statement return "\n".join(schema_editor.collected_sql)
def test_custom_manager_swappable(self): """ Tests making a ProjectState from unused models with custom managers """ new_apps = Apps(['migrations']) 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 swappable = 'TEST_SWAPPABLE_MODEL' food_state = ModelState.from_model(Food) # 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))
def build_graph(self, ignore_unmigrated=False): """ Builds a migration dependency graph using both the disk and database. You'll need to rebuild the graph if you apply migrations. This isn't usually a problem as generally migration stuff runs in a one-shot process. """ # Load disk data self.load_disk() # Load database data recorder = MigrationRecorder(self.connection) self.applied_migrations = recorder.applied_migrations() # Do a first pass to separate out replacing and non-replacing migrations normal = {} replacing = {} for key, migration in self.disk_migrations.items(): if migration.replaces: replacing[key] = migration else: normal[key] = migration # Calculate reverse dependencies - i.e., for each migration, what depends on it? # This is just for dependency re-pointing when applying replacements, # so we ignore run_before here. reverse_dependencies = {} for key, migration in normal.items(): for parent in migration.dependencies: reverse_dependencies.setdefault(parent, set()).add(key) # Carry out replacements if we can - that is, if all replaced migrations # are either unapplied or missing. for key, migration in replacing.items(): # Ensure this replacement migration is not in applied_migrations self.applied_migrations.discard(key) # Do the check. We can replace if all our replace targets are # applied, or if all of them are unapplied. applied_statuses = [(target in self.applied_migrations) for target in migration.replaces] can_replace = all(applied_statuses) or (not any(applied_statuses)) if not can_replace: continue # Alright, time to replace. Step through the replaced migrations # and remove, repointing dependencies if needs be. for replaced in migration.replaces: if replaced in normal: # We don't care if the replaced migration doesn't exist; # the usage pattern here is to delete things after a while. del normal[replaced] for child_key in reverse_dependencies.get(replaced, set()): if child_key in migration.replaces: continue normal[child_key].dependencies.remove(replaced) normal[child_key].dependencies.append(key) normal[key] = migration # Mark the replacement as applied if all its replaced ones are if all(applied_statuses): self.applied_migrations.add(key) # Finally, make a graph and load everything into it self.graph = MigrationGraph() for key, migration in normal.items(): self.graph.add_node(key, migration) for key, migration in normal.items(): for parent in migration.dependencies: # Special-case __first__, which means "the first migration" for # migrated apps, and is ignored for unmigrated apps. It allows # makemigrations to declare dependencies on apps before they # even have migrations. if parent[1] == "__first__" and parent not in self.graph: if parent[0] in self.unmigrated_apps: if ignore_unmigrated: parent = None else: # This app isn't migrated, but something depends on it. # We'll add a fake initial migration for it into the # graph. app_config = apps.get_app_config(parent[0]) ops = [] for model in app_config.get_models(): model_state = ModelState.from_model(model) ops.append( operations.CreateModel( name=model_state.name, fields=model_state.fields, options=model_state.options, bases=model_state.bases, ) ) new_migration = type( "FakeInitialMigration", (Migration, ), {"operations": ops}, )(parent[1], parent[0]) self.graph.add_node(parent, new_migration) self.applied_migrations.add(parent) elif parent[0] in self.migrated_apps: parent = list(self.graph.root_nodes(parent[0]))[0] else: raise ValueError("Dependency on unknown app %s" % parent[0]) if parent is not None: self.graph.add_dependency(key, parent)
def test_render_model_with_multiple_inheritance(self): class Foo(models.Model): class Meta: app_label = "migrations" apps = Apps() class Bar(models.Model): class Meta: app_label = "migrations" apps = Apps() class FooBar(Foo, Bar): class Meta: app_label = "migrations" apps = Apps() class AbstractSubFooBar(FooBar): class Meta: abstract = True apps = Apps() class SubFooBar(AbstractSubFooBar): class Meta: app_label = "migrations" apps = Apps() apps = Apps(["migrations"]) # We shouldn't be able to render yet ms = ModelState.from_model(FooBar) with self.assertRaises(InvalidBasesError): ms.render(apps) # Once the parent models are in the app registry, it should be fine ModelState.from_model(Foo).render(apps) self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model]) ModelState.from_model(Bar).render(apps) self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model]) ModelState.from_model(FooBar).render(apps) self.assertSequenceEqual(ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar']) ModelState.from_model(SubFooBar).render(apps) self.assertSequenceEqual(ModelState.from_model(SubFooBar).bases, ['migrations.foobar'])
def get_model_state(cls, **kwargs): return ModelState.from_model(cls, **kwargs)
def test_custom_model_base(self): state = ModelState.from_model(ModelWithCustomBase) self.assertEqual(state.bases, (models.Model,))
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 test_render_model_with_multiple_inheritance(self): class Foo(models.Model): class Meta: app_label = "migrations" apps = Apps() class Bar(models.Model): class Meta: app_label = "migrations" apps = Apps() class FooBar(Foo, Bar): class Meta: app_label = "migrations" apps = Apps() class AbstractSubFooBar(FooBar): class Meta: abstract = True apps = Apps() class SubFooBar(AbstractSubFooBar): class Meta: app_label = "migrations" apps = Apps() apps = Apps(["migrations"]) # We shouldn't be able to render yet ms = ModelState.from_model(FooBar) with self.assertRaises(InvalidBasesError): ms.render(apps) # Once the parent models are in the app registry, it should be fine ModelState.from_model(Foo).render(apps) self.assertSequenceEqual( ModelState.from_model(Foo).bases, [models.Model]) ModelState.from_model(Bar).render(apps) self.assertSequenceEqual( ModelState.from_model(Bar).bases, [models.Model]) ModelState.from_model(FooBar).render(apps) self.assertSequenceEqual( ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar']) ModelState.from_model(SubFooBar).render(apps) self.assertSequenceEqual( ModelState.from_model(SubFooBar).bases, ['migrations.foobar'])
def handle(self, *args, **options): self.branch = options['branch'] 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) targets = [] migrations = {} migration_paths = self.get_diff_migrations() # Group diff migrations by app_label for p in migration_paths: app_label = p.parts[0] if app_label not in migrations: # Validate app_label. try: apps.get_app_config(app_label) except LookupError as err: raise CommandError(str(err)) migrations[app_label] = [] migrations[app_label].append(p.stem) # Get last applied migration for each app for app_label, migration_names in migrations.items(): mr = MigrationRecorder.Migration.objects.filter(app=app_label) # Skip if already unapplied if not mr.filter(name__in=migration_names).count(): continue mr = mr.exclude( name__in=migration_names).order_by('applied').last() if mr: targets.append((app_label, mr.name)) if not targets: if self.verbosity >= 1: self.stdout.write(" No migrations to unapply.") return 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 # Print some useful info if self.verbosity >= 1: self.stdout.write( self.style.MIGRATE_HEADING("Operations to perform:")) for app_label, migration_name in targets: self.stdout.write( self.style.MIGRATE_LABEL(" Target specific migration: ") + "%s, from %s" % (app_label, migration_name)) # noinspection PyProtectedMember 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, ) # Migrate! if self.verbosity >= 1: self.stdout.write( self.style.MIGRATE_HEADING("Running migrations:")) if not plan: 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 _get_state(models): project_state = ProjectState.from_apps(apps) for model in models: model_state = ModelState.from_model(model) project_state.add_model(model_state) return project_state
def handle(self, *args, **options): 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 BLOG, 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 BLOG '%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 BLOG '%s'. " "Please be more specific." % (migration_name, app_label) ) except KeyError: raise CommandError("Cannot find a migration matching '%s' from BLOG '%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) 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 BLOG: %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 in BLOG(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, apps=post_migrate_apps, plan=plan, )
def handle(self, *args, **options): self.verbosity = options['verbosity'] self.interactive = options['interactive'] # Import the 'management' module within each installed app, to register # dispatcher events. for app_config in apps.get_app_configs(): if module_has_submodule(app_config.module, "management"): import_module('.management', app_config.name) # Get the database we're operating from db = options['database'] connection = connections[db] # Hook for backends needing any database preparation connection.prepare_database() # Work out which apps have migrations and which do not executor = MigrationExecutor(connection, self.migration_progress_callback) # Raise an error if any migrations are applied before their dependencies. executor.loader.check_consistent_history(connection) # Before anything else, see if there's conflicting apps and drop out # hard if there are any conflicts = executor.loader.detect_conflicts() if conflicts: name_str = "; ".join( "%s in %s" % (", ".join(names), app) for app, names in conflicts.items() ) raise CommandError( "Conflicting migrations detected; multiple leaf nodes in the " "migration graph: (%s).\nTo fix them run " "'python manage.py makemigrations --merge'" % name_str ) # If they supplied command line arguments, work out what they mean. target_app_labels_only = True if options['app_label']: # Validate app_label. app_label = options['app_label'] try: apps.get_app_config(app_label) except LookupError as err: raise CommandError(str(err)) if app_label not in executor.loader.migrated_apps: raise CommandError("App '%s' does not have migrations." % app_label) if options['app_label'] and options['migration_name']: migration_name = options['migration_name'] if migration_name == "zero": targets = [(app_label, None)] else: try: migration = executor.loader.get_migration_by_prefix(app_label, migration_name) except AmbiguityError: raise CommandError( "More than one migration matches '%s' in app '%s'. " "Please be more specific." % (migration_name, app_label) ) except KeyError: raise CommandError("Cannot find a migration matching '%s' from app '%s'." % ( migration_name, app_label)) targets = [(app_label, migration.name)] target_app_labels_only = False elif options['app_label']: targets = [key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label] else: targets = executor.loader.graph.leaf_nodes() plan = executor.migration_plan(targets) if options['plan']: self.stdout.write('Planned operations:', self.style.MIGRATE_LABEL) if not plan: self.stdout.write(' No planned migration operations.') for migration, backwards in plan: self.stdout.write(str(migration), self.style.MIGRATE_HEADING) for operation in migration.operations: message, is_error = self.describe_operation(operation, backwards) style = self.style.WARNING if is_error else None self.stdout.write(' ' + message, style) return run_syncdb = options['run_syncdb'] and executor.loader.unmigrated_apps # Print some useful info if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:")) if run_syncdb: self.stdout.write( self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + (", ".join(sorted(executor.loader.unmigrated_apps))) ) if target_app_labels_only: self.stdout.write( self.style.MIGRATE_LABEL(" Apply all migrations: ") + (", ".join(sorted({a for a, n in targets})) or "(none)") ) else: if targets[0][1] is None: self.stdout.write(self.style.MIGRATE_LABEL( " Unapply all migrations: ") + "%s" % (targets[0][0],) ) else: self.stdout.write(self.style.MIGRATE_LABEL( " Target specific migration: ") + "%s, from %s" % (targets[0][1], targets[0][0]) ) pre_migrate_state = executor._create_project_state(with_applied_migrations=True) pre_migrate_apps = pre_migrate_state.apps emit_pre_migrate_signal( self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan, ) # Run the syncdb phase. if run_syncdb: if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:")) self.sync_apps(connection, executor.loader.unmigrated_apps) # Migrate! if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:")) if not plan: if self.verbosity >= 1: self.stdout.write(" No migrations to apply.") # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: self.stdout.write(self.style.NOTICE( " Your models have changes that are not yet reflected " "in a migration, and so won't be applied." )) self.stdout.write(self.style.NOTICE( " Run 'manage.py makemigrations' to make new " "migrations, and then re-run 'manage.py migrate' to " "apply them." )) fake = False fake_initial = False else: fake = options['fake'] fake_initial = options['fake_initial'] post_migrate_state = executor.migrate( targets, plan=plan, state=pre_migrate_state.clone(), fake=fake, fake_initial=fake_initial, ) # post_migrate signals have access to all models. Ensure that all models # are reloaded in case any are delayed. post_migrate_state.clear_delayed_apps_cache() post_migrate_apps = post_migrate_state.apps # Re-render models of real apps to include relationships now that # we've got a final state. This wouldn't be necessary if real apps # models were rendered with relationships in the first place. with post_migrate_apps.bulk_update(): model_keys = [] for model_state in post_migrate_apps.real_models: model_key = model_state.app_label, model_state.name_lower model_keys.append(model_key) post_migrate_apps.unregister_model(*model_key) post_migrate_apps.render_multiple([ ModelState.from_model(apps.get_model(*model)) for model in model_keys ]) # Send the post_migrate signal, so individual apps can do whatever they need # to do at this point. emit_post_migrate_signal( self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan, )
def test_custom_model_base(self): state = ModelState.from_model(ModelWithCustomBase) self.assertEqual(state.bases, (models.Model, ))
def _migrate(self, plan, connection, executor, pre_migrate_state, targets, options): 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 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 build_graph(self): """ Builds a migration dependency graph using both the disk and database. You'll need to rebuild the graph if you apply migrations. This isn't usually a problem as generally migration stuff runs in a one-shot process. """ # Load disk data self.load_disk() # Load database data recorder = MigrationRecorder(self.connection) self.applied_migrations = recorder.applied_migrations() # Do a first pass to separate out replacing and non-replacing migrations normal = {} replacing = {} for key, migration in self.disk_migrations.items(): if migration.replaces: replacing[key] = migration else: normal[key] = migration # Calculate reverse dependencies - i.e., for each migration, what depends on it? # This is just for dependency re-pointing when applying replacements, # so we ignore run_before here. reverse_dependencies = {} for key, migration in normal.items(): for parent in migration.dependencies: reverse_dependencies.setdefault(parent, set()).add(key) # Carry out replacements if we can - that is, if all replaced migrations # are either unapplied or missing. for key, migration in replacing.items(): # Ensure this replacement migration is not in applied_migrations self.applied_migrations.discard(key) # Do the check. We can replace if all our replace targets are # applied, or if all of them are unapplied. applied_statuses = [(target in self.applied_migrations) for target in migration.replaces] can_replace = all(applied_statuses) or (not any(applied_statuses)) if not can_replace: continue # Alright, time to replace. Step through the replaced migrations # and remove, repointing dependencies if needs be. for replaced in migration.replaces: if replaced in normal: # We don't care if the replaced migration doesn't exist; # the usage pattern here is to delete things after a while. del normal[replaced] for child_key in reverse_dependencies.get(replaced, set()): if child_key in migration.replaces: continue normal[child_key].dependencies.remove(replaced) normal[child_key].dependencies.append(key) normal[key] = migration # Mark the replacement as applied if all its replaced ones are if all(applied_statuses): self.applied_migrations.add(key) # Finally, make a graph and load everything into it self.graph = MigrationGraph() for key, migration in normal.items(): self.graph.add_node(key, migration) for key, migration in normal.items(): for parent in migration.dependencies: # Special-case __first__, which means "the first migration" for # migrated apps, and is ignored for unmigrated apps. It allows # makemigrations to declare dependencies on apps before they # even have migrations. if parent[1] == "__first__" and parent not in self.graph: if parent[0] in self.unmigrated_apps: # This app isn't migrated, but something depends on it. # We'll add a fake initial migration for it into the # graph. app_config = apps.get_app_config(parent[0]) ops = [] for model in app_config.get_models(): model_state = ModelState.from_model(model) ops.append( operations.CreateModel( name=model_state.name, fields=model_state.fields, options=model_state.options, bases=model_state.bases, )) new_migration = type( "FakeInitialMigration", (Migration, ), {"operations": ops}, )(parent[1], parent[0]) self.graph.add_node(parent, new_migration) self.applied_migrations.add(parent) elif parent[0] in self.migrated_apps: parent = (parent[0], list(self.graph.root_nodes(parent[0]))[0]) else: raise ValueError("Dependency on unknown app %s" % parent[0]) self.graph.add_dependency(key, parent)
def from_model(cls, model, exclude_rels=False): result = ModelState.from_model(model, exclude_rels=exclude_rels) if getattr(model._meta, '_is_view', False): result.options['db_table'] = model._meta._base_model._meta.db_table return result