10
votes

I have an existing Django project that has several models using concrete inheritance of a base class. After closer consideration, and after reading about what people like Jacob Kaplan-Moss have to say about it, using this concrete inheritance is unnecessary in my case. I would like to migrate to using an abstract base class instead.

The thing that makes this complicated is that my site is live and I have user entered data. Thus, I'll need to keep all my data intact throughout this transition.

I'll give an example to be more concrete:

Before:

app1/models.py:

class Model1(base_app.models.BaseModel):
    field1 = models.CharField(max_length=1000)
    field2 = models.CharField(max_length=1000)

app2/models.py:

class Model2(base_app.models.BaseModel):
    field1 = models.CharField(max_length=1000)
    field2 = models.CharField(max_length=1000)

base_app/models.py:

class BaseModel(models.Model):
    user = models.ForeignKey(User)
    another_field = models.CharField(max_length=1000)

After:

app1/models.py:

class Model1(base_app.models.BaseModel):
    field1 = models.CharField(max_length=1000)
    field2 = models.CharField(max_length=1000)

app2/models.py:

class Model2(base_app.models.BaseModel):
    field1 = models.CharField(max_length=1000)
    field2 = models.CharField(max_length=1000)

base_app/models.py:

class BaseModel(models.Model):
    user = models.ForeignKey(User)
    another_field = models.CharField(max_length=1000)

    class Meta:
        abstract = True

Right now, my plan is to first add the abstract = True to the BaseModel. Then,for each model that uses BaseModel, one at a time:

  • Use south to migrate the database and create this migration using the --auto flag
  • Use a south data migration. For instance, I would loop through each object in Model1 to fetch the object in BaseModel that has the same pk and copy the values for each field of the BaseModel object to the Model1 object.

So first, will this work? And second, is there a better way to do this?

Update:

My final solution is described in detail here:

http://www.markliu.me/2011/aug/23/migrating-a-django-postgres-db-from-concrete-inhe/

2
You're not using Abstract Base Class the right way. You need to put the class Meta: abstract = True on your BaseModel, not on his descendants. docs.djangoproject.com/en/1.3/topics/db/models/…Etienne
oops, of course... i made a change to reflect thisSpike
The link is broken :/Medeiros
@Medeiros sorry about that! I just updated it so it should be working okay again.Spike

2 Answers

7
votes
  1. Add NewBaseModel, we use different name so it doesn't conflict with current non-abstract one (South would actually delete BaseModel otherwise).

    class NewBaseModel(models.Model):
        user = models.ForeignKey(User)
        another_field = models.CharField(max_length=1000)
    
        class Meta:
            abstract = True
    
  2. Set Model1 and Model2 to inherit from NewBaseModel

  3. Run schemamigration --auto, 2 new fields will be added to Model1 and Model2
  4. Run datamigration --empty and fill new fields from values in BaseModel
  5. Load production db and double check everything migrated correctly
  6. Remove BaseModel and rename NewBaseModel to BaseModel
  7. Run schemamigration --auto (this should work ;) )
  8. Deploy!

NOTE: Use orm variable when migrating to use current state of your model schema.

1
votes

Sebastjan Trepča's answer is probably good but, another way to do it will be to create your migration manually:

  1. Add the abstract = True to your base model.

  2. Run schemamigration --auto, the generated migration will probably not be good but you will use it as a base.

  3. Edit the migration file. In the forward you should add, in this order:

    a. db.delete_foreign_key(table_name, column) for each of your children models. This will remove the ForeignKey between the parent and the children table.

    b. db.delete_table(BaseModel) to delete the table of the base model (this command should be probably there already, generated by --auto).

    c. It's possible that you will have to rename all the primary key column of your children models to 'id'. I'm not sure about this. If you need to do this: db.rename_column(table_name, column_name, 'id') for each of your children models.

  4. Remove all auto-generated code in forward that doesn't make sense.

  5. Run the migration: migrate

What this method is doing is removing the table of the base class and the foreign keys between the base class table and its children because they are not use with the Abstract Base Class.

I didn't test this method so it's possible that you'll hit some problems. This approach is more complicated then the other one but the advantages are that you don't need to migrate the data and that you will understand what is happening. It should run also pretty fast, a good thing for a live migration.

You can consult the South API for more info.

One really important thing, in any method you will use, work on a local copy of your system and database. When you will be really sure that the migration is working well, backup your production DB then apply your migration and then restart your webserver (to load your modified model code).