Как создать суперкласс (для существующей модели) с Django и South

Допустим, у меня есть приложение Django с именем animals, которое содержит модель с именем Cat, которая уже заполнена строками в базе данных:

class Cat(models.Model):
    objects = models.Manager() 

    name = models.CharField(max_length=255)
    miaow_factor = models.IntegerField()

Как лучше всего создать суперкласс (например, Feline) Cat и добавить его в базу данных с помощью миграции на юг? то есть я хотел бы получить структуру:

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()

Имейте в виду, что:

  • Cat.feline должен быть новым первичным ключом Cat, заменяющим Cat.id

  • Значения Cat.id и Cat.feline должны быть одинаковыми для всех объектов Cat (чтобы все ForeignKeys для Cat оставались действительными).

  • Новые объекты Cat или Feline должны иметь идентификаторы, назначенные после самого большого идентификатора Cat, иначе вы в конечном итоге попытаетесь выделить уже использованный идентификатор для нового Cat или Feline.

  • Любые представления базы данных, зависящие от Cat.id, не должны удаляться (поэтому вы не можете удалить Cat.id, потому что это приведет к каскадному удалению этих представлений)

Поработав над этой проблемой и вышеуказанными проблемами в течение последних нескольких дней, у меня есть решение, которое включает (среди прочего) переименование Cat.id в Cat.feline_id, которое я дам в качестве ответа ниже - любые другие альтернативы приветствуются.


person jcdude    schedule 22.01.2014    source источник
comment
Я думаю, что сбросил бы базу данных, внесу изменения в модель, перенесу, а затем восстановлю (добавив cat_id в таблицу кошачьих по мере того, как я пошел). Но это только я. Я думаю, это займет час, а не дни.   -  person Rob L    schedule 22.01.2014
comment
конечно, но это не очень полезно, если вы хотите, чтобы ваша миграция могла идти вперед и назад, повторяясь, в разных экземплярах (например, dev, live) вашей базы данных   -  person jcdude    schedule 29.01.2014


Ответы (1)


Мое решение работает следующим образом. Сначала добавьте модель Feline, но оставьте модель Cat без изменений:

class Feline(models.Model):
    objects = models.Manager() 

class Cat(models.Model):
    objects = models.Manager() 

    name = models.CharField(max_length=255)
    miaow_factor = models.IntegerField()

Затем создайте и запустите миграцию схемы (manage.py schemamigration animals --auto), чтобы добавить модель Feline в базу данных.

Затем создайте перенос данных (manage.py datamigration animals cat_feline). В миграции данных добавьте код для создания кошачьих для каждой кошки, чтобы каждая созданная кошка имела общий идентификатор с кошкой. Кроме того, измените последовательность для новых животных из семейства кошачьих, чтобы всем новым животным из семейства кошачьих назначались идентификаторы, превышающие самый большой текущий идентификатор кошки.

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 

Затем измените файл моделей, чтобы Cat унаследовал от Feline, и добавьте OneToOneField в Cat, который будет новым первичным ключом для 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()

Затем создайте еще одну миграцию схемы, чтобы применить эти изменения к базе данных. Однако не выполняйте перенос. Вместо этого измените код в миграции, чтобы переименовать столбец Cat.id в 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)) 

Наконец, запустите только что отредактированную схему миграции, и все готово.

Теперь, если вы хотите, вы можете легко переместить некоторые поля (например, имя) из Cat в Feline, используя дальнейшие схемы и миграции данных.

Еще одна проблема (с которой мне, к счастью, не приходилось сталкиваться) будет, если вы захотите создать суперкласс для нескольких существующих моделей - в этом случае вы не сможете сохранить один и тот же идентификатор для всех экземпляров, как некоторые экземпляры в два разных подкласса могут конфликтовать по идентификатору. Приветствуются любые мысли о том, как обойти это.

person jcdude    schedule 22.01.2014
comment
Я хотел бы добавить несколько ссылок на источники, которые помогли мне на этом пути. Добавление поля OneToOne в существующую модель: stackoverflow.com/questions/8586703/ Изменение следующего идентификатора в последовательности первичного ключа: vlent.nl/weblog/2011/05 / 06 / - person jcdude; 22.01.2014
comment
Также: изменение AutoField на поле, подобное ForeignKey, и обратно: south.aeracode.org/ticket/ 407 Создание суперкласса для нескольких существующих классов (но без общих идентификаторов): stackoverflow.com/questions/1600129/ - person jcdude; 22.01.2014