Search code examples
djangodjango-modelsdjango-south

Modify a model field's data using South


I have a model which I want to rename but also change its data. The model field, sayfoo contains char data 1 ... 6 and I want to map this data in following ways:

1, 2 -> 1 && 3, 4 -> 2 && 5 -> 3 && 6 -> 4

I did convert_to_south on my app, which created 0001_initial and then changed new new field in models.py and did schemamigration which made 0002_initial.py. Now how do I modify the existing data? Here are my migrations:

0001_initial

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Adding model 'Applicant'
        db.create_table(u'registrar_applicant', (
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('registered_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
            ('workshop', self.gf('django.db.models.fields.CharField')(max_length=20)),
            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
            ('semester', self.gf('django.db.models.fields.CharField')(max_length=1)),
            ('python_experience', self.gf('django.db.models.fields.CharField')(max_length=1)),
            ('phone_number', self.gf('django.db.models.fields.CharField')(max_length=10, blank=True)),
            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)),
            ('solved_puzzle', self.gf('django.db.models.fields.BooleanField')(default=False)),
        ))
        db.send_create_signal(u'registrar', ['Applicant'])


    def backwards(self, orm):
        # Deleting model 'Applicant'
        db.delete_table(u'registrar_applicant')


    models = {
        u'registrar.applicant': {
            'Meta': {'ordering': "['-solved_puzzle', 'registered_at', 'semester', 'name']", 'object_name': 'Applicant'},
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
            'python_experience': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
            'registered_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
            'semester': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
            'solved_puzzle': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'workshop': ('django.db.models.fields.CharField', [], {'max_length': '20'})
        }
    }

    complete_apps = ['registrar']

0002_initial

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Adding model 'Applicant'
        db.create_table(u'registrar_applicant', (
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('registered_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
            ('workshop', self.gf('django.db.models.fields.CharField')(max_length=20)),
            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
            ('year', self.gf('django.db.models.fields.CharField')(max_length=1)),
            ('python_experience', self.gf('django.db.models.fields.CharField')(max_length=1)),
            ('phone_number', self.gf('django.db.models.fields.CharField')(max_length=10, blank=True)),
            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)),
            ('solved_puzzle', self.gf('django.db.models.fields.BooleanField')(default=False)),
        ))
        db.send_create_signal(u'registrar', ['Applicant'])


    def backwards(self, orm):
        # Deleting model 'Applicant'
        db.delete_table(u'registrar_applicant')


    models = {
        u'registrar.applicant': {
            'Meta': {'ordering': "['-solved_puzzle', 'registered_at', 'year', 'name']", 'object_name': 'Applicant'},
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
            'python_experience': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
            'registered_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
            'solved_puzzle': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'workshop': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
            'year': ('django.db.models.fields.CharField', [], {'max_length': '1'})
        }
    }

    complete_apps = ['registrar']

Solution

  • For this, you'll have to create a data migration

    # Run this command from the shell
    python manage.py datamigration <app_name> change_foo_values
    

    This is will create a migration file named 0003_change_foo_values.py. You will have to manually edit the file.

    # -*- coding: utf-8 -*-
    from south.utils import datetime_utils as datetime
    from south.db import db
    from south.v2 import DataMigration
    from django.db import models
    
    class Migration(DataMigration):
    
        def forwards(self, orm):
            "Write your forwards methods here."
            # Note: Don't use "from appname.models import ModelName". 
            # Use orm.ModelName to refer to models in this application,
            # and orm['appname.ModelName'] for models in other applications.
            orm.Applicant.objects.filter(foo__in=['1', '2']).update(foo='1')
            orm.Applicant.objects.filter(foo__in=['3', '4']).update(foo='2')
            orm.Applicant.objects.filter(foo__in=['5', '6']).update(foo='3')
    
        def backwards(self, orm):
            "Write your backwards methods here."
    
             # This migration cannot be reversed
             pass    # don't do anything when running reverse migration
             # or
             raise RuntimeError("Cannot reverse this migration") # stop south from reverting beyond this migration
    

    Note: You won't be able to go revert back to the previous values of the column. If that is not a problem then this is sufficient.

    However, if you do want to be able to reverse the migration, I suggest creating a new field, that will hold the old value of the column for each row.

    PS: Your migration files seem wrong. You should not have two initial migrations for one app. Did you run convert_to_south after renaming your model(which doesn't seem to have been renamed) again? You only need to run convert_to_south once, when initially converting an existing model to south. After that you need to create schema migrations. The simplest way would be to run the schemamigration management command with the appname, and --auto as arguments.

    python manage.py schemamigration <app_name> --auto
    

    PPS: If you want to rename a model, please create an empty migration and write the migration code yourself, just use db.rename_table. IIRC south will delete your old table before creating a new one if you try to create auto schemamigrations after renaming the model.

    python manage.py schemamigration <app_name> --empty rename_model_x_to_y
    

    And in your migration file

    # -*- coding: utf-8 -*-
    from south.utils import datetime_utils as datetime
    from south.db import db
    from south.v2 import DataMigration
    from django.db import models
    
    class Migration(SchemaMigration):
    
        def forwards(self, orm):
            "Write your forwards methods here."
            # Note: Don't use "from appname.models import ModelName". 
            # Use orm.ModelName to refer to models in this application,
            # and orm['appname.ModelName'] for models in other applications.
            db.rename_table('old', 'new')
    
        def backwards(self, orm):
            "Write your backwards methods here."
            db.rename_table('new', 'old')