[Fixed]-Django south: changing field type in data migration

19👍

The correct way to do it would be to break this into three migrations:

  1. A first schema migration to add the new IntegerField field.
  2. Followed by a data migration to convert the data originating from the CharField to the IntegerField
  3. And a final schema migration to remove the now unused CharField.

A fourth one might be required if you want your newly added IntegerField to have the same name as the to-be-removed CharField.

Given a project state where the IntegerField is not yet added to you model file you should proceed by following these steps:

  1. Add the IntegerField to you model.
  2. Create a schema migration for the application containing your model. You might need to specify a default value for your newly added field here if it’s not null.
  3. Create a data migration (using datamigration) for the application containing your model. In the forwards() method of the newly created DataMigration class write down your logic to convert your data. Try to use the update manager method instead of iterating overt all your database row if possible. If you declare your conversion logic using a dict (say {'L': 1, ...}) it should be pretty easy to also implement backwards() at this time, given the operation is invertible. It’s also a good exercise to make sure you’ve not overlooked an edged case in forwards() — it helped me quite a few times in the past.
  4. Remove the CharField from your model.
  5. Create a schema migration for the application containing your model in order to DROP the now unused column.

The fact that you broke this operation into three migrations instead of writing down your whole logic in a blank template have a couple advantages:

  1. Auto-generated DDL operations: The ADD/DROP logic have been automatically generated by South and you don’t have to worry about introducing a typo in a database column.
  2. Completely reversible operation: Given you’ve taken the time to implement DataMigration.backwards() for the conversion step you should be able to completely reverse the whole operation. This can be handy for testing purpose, if you need to rollback to a previous revision of your code and as safe net when updating a production code base.
  3. Atomicity of operations: The fact that each operation is isolated and run in it’s own transaction won’t leave you in an inconsistent state between your database and your South migration. For example, if all operations were performed in a single migration (in the same forwards() method in this case) and an exception was raised during the data migration step (say a KeyError because of an unhandled value in your conversion dict). If you’re using a ORDBMS that doesn’t support transactional schema alteration you wouldn’t be able to re-run you migration immediately after fixing the data-migration part, you’d have to manually drop the newly added IntegerField column yourself. Again, that’s the kind of thing you don’t want to deal with when migrating a production database.
  4. Ability to perform a dry-run: Also quite handy when migrating a production database.

1👍

for the same situation me followed just 3 steps .

1) changed file type from CharField to IntegerField,

2) ALTER TABLE the_table ALTER COLUMN col_name TYPE integer USING (col_name::integer);
or

ALTER TABLE the_table ALTER COLUMN col_name TYPE integer USING (trim(col_name)::integer); # in case if you having whitespace in your Char or Text field. and if you having data’s in your table, which should be string of integer.

3) Now apply migration.

Leave a comment