Adding non-zero ForeignKey field in Django + South

I use Django and South for my database. Now I want to add a new model and field to an existing model, referencing the new model. For example:

class NewModel(models.Model): # a new model # ... class ExistingModel(models.Model): # ... existing fields new_field = models.ForeignKey(NewModel) # adding this now 

Now the South is clearly complaining that it added a nonzero field and asked for a one-time value. But I really want to create a new instance of NewModel for each existing instance of ExistingModel , thereby fulfilling the database requirements. Is it possible somehow?

+7
source share
2 answers

You want data migration to complement schema migration in this scenario.

The South has a good step-by-step tutorial on how to do this in the documents, here .

In the South, it is not uncommon to have the desired result that extends to two or three schema / data migrations, since it can not always be done with one hit (sometimes it depends on the base db if it will tolerate adding a column without zero without default). Thus, in this case, you can add the migration of the schema, which has a default value, and then the migration of data by manipulating the object, and then the final migration of the schema.

+6
source

The easiest way to do this is to write a schema migration that will change the value of the column, and then write datamigration to fill the value correctly. Depending on the database you are using, you will have to do this a little differently.

Sqlite

For Sqlite, you can add a control value for the relationship and use datamigration to populate it without any problems:

0001_schema_migration_add_foreign_key_to_new_model_from_existing_model.py

 from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): db.add_column('existing_model_table', 'new_model', self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['appname.new_model']), keep_default=False) 

0002_data_migration_for_new_model.py :

 class Migration(DataMigration): def forwards(self, orm): for m in orm['appname.existing_model'].objects.all(): m.new_model = #custom criteria here m.save() 

This will work fine, no problem.

Postgres and MySQL

With MySql, you must give it a valid default. If 0 is not really a valid foreign key, you will receive an error message.

You can use the value 1 by default, but there are cases when this is not a valid foreign key (happened to me because we have different environments, and some environments are published in other databases, so the identifiers rarely match (we use UUIDs for identification cross-database, as God intended).

The second problem is that South and MySQL do not play together. Partly because MySQL does not have the concept of DDL transactions.

In order to get around some problems, you will inevitably encounter (including the error that I mentioned above, and from the South I ask you to mark orm elements in SchemaMigration as no-dry-run ), you need to change the above 0001 script to do the following:

0001_schema_migration_add_foreign_key_to_new_model_from_existing_model.py

 from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): id = 0 if not db.dry_run: new_model = orm['appname.new_model'].objects.all()[0] if new_model: id = new_model.id db.add_column('existing_model_table', 'new_model', self.gf('django.db.models.fields.related.ForeignKey')(default=id, to=orm['appname.new_model']), keep_default=False) 

And then you can run the file 0002_data_migration_for_new_model.py as usual.

I suggest using the same example above for Postgres and for MySql. I do not remember any problems with Postgres with the first example, but I am sure that the second example works for both (verified) ones.

+9
source

All Articles