My solution works as follows. First, add the Feline model, but leave the Cat model unchanged:
class Feline(models.Model):
objects = models.Manager()
class Cat(models.Model):
objects = models.Manager()
name = models.CharField(max_length=255)
miaow_factor = models.IntegerField()
Next, create and run a schemamigration (manage.py schemamigration animals --auto
), in order to add the Feline
model to the database.
Next, create a datamigration, (manage.py datamigration animals cat_feline
). In the datamigration, add code to create a Feline for each Cat, such that each Feline that is created shares an ID with a Cat. Additionally, change the sequence for new Felines so that all new Felines are allocated IDs that are larger than the largest current Cat ID.
class Migration(DataMigration):
def forwards(self, orm):
#create a Feline for each Cat
for c in orm['Cat'].objects.all():
f = orm['Feline']()
f.id = c.id
f.save()
if orm['Feline'].objects.count():
#if there are any Feline objects, make sure that new ids are allocated after the largest current ID
last_id = orm['Feline'].objects.latest('id').id
db.execute('alter sequence animals_feline_id_seq restart with %s;' % (last_id + 1))
def backwards(self, orm):
#no need to do anything if migrating backwards
pass
Next, change the models file to make Cat inherit from Feline, and add the OneToOneField to Cat which will be the new primary key for Cats:
class Feline(models.Model):
objects = models.Manager()
class Cat(Feline):
#n.b. Cat now inherits from Feline, not models.Model
objects = models.Manager()
feline = models.OneToOneField(Feline, parent_link = True)
name = models.CharField(max_length=255)
miaow_factor = models.IntegerField()
Next, create another schemamigration, in order to apply these changes to the database. However, don't run the migration. Instead, change the code in the migration to rename the Cat.id
column to Cat.feline_id
class Migration(SchemaMigration):
def forwards(self, orm):
#original changes generated by South:
# Deleting field 'Cat.id'
#db.delete_column(u'animals_cat', u'id')
# Adding field 'Cat.feline'
#db.add_column(u'animals_cat', 'feline',
# self.gf('django.db.models.fields.related.OneToOneField')(default=None, to=orm['animals.Feline'], unique=True, primary_key=True),
# keep_default=False)
#instead of doing the above, just rename Cat.id to Cat.feline_id
#and drop the default numbering sequence for the Cat.feline_id field
db.rename_column('animals_cat', 'id', 'feline_id')
db.execute("ALTER TABLE animals_cat ALTER COLUMN feline_id DROP DEFAULT")
def backwards(self, orm):
#original changes generated by South:
# Adding field 'Cat.id'
#db.add_column('animals_cat', u'id',
# self.gf('django.db.models.fields.AutoField')(default=None, primary_key=True),
# keep_default=False)
# Deleting field 'Cat.feline_id'
#db.delete_column(u'animals_cat', 'feline_id')
#instead of doing the above, rename Cat.feline_id to Cat.id
#and reinstate the default numbering sequence for the Cat.id field
db.rename_column('animals_cat', 'feline_id', 'id')
db.execute("ALTER TABLE animals_cat ALTER COLUMN id SET DEFAULT nextval('u'animals_cat_id_seq'::regclass)")
if orm['Cat'].objects.count():
#if there are any Cat objects, make sure that new ids are allocated after the largest current ID
last_id = orm['Cat'].objects.latest('id').id
db.execute('alter sequence animals_cat_id_seq restart with %s;' % (last_id + 1))
Finally, run the schemamigration that you have just edited, and you're done.
Now, if you wish, you can easily move some fields (e.g. name) from Cat to Feline, using further schemamigrations and datamigrations.
A further challenge (that I have not had to deal with, luckily) would be if you wanted to create a superclass for multiple existing models - in that case you may not be able to keep the same ID for all instances, as some instances in the two different subclasses might clash on ID. Any thoughts on how to work around this would be welcome.