Можно ли объединить миграцию схемы и данных (Юг) в одну?

Я хочу переместить поле гудок и его данные из одной модели в другую, используя Юг:

class Foo(models.Model):
    foofield = models.CharField()
    honk = models.PositiveIntegerField()

class Bar(models.Model):
    barfield = models.CharField()

Я делал это раньше, используя 3 отдельные миграции:

  1. Миграция схемы, добавление сигнала в Bar
  2. Миграция данных, копирование всех данных Foo.honk в Bar.honk.
  3. Еще одна миграция схемы, удаление сигнала от Foo

Могу ли я выполнить эти три шага за одну миграцию?

Я уже узнал, что не так много разница между миграцией схемы и данных в South, поэтому я подумал, что, возможно, что-то вроде этого может сработать (это три миграции выше, просто объединенные в одну):

class Migration(DataMigration):
    def forwards(self, orm):
        # add column
        db.add_column('myapp_bar', 'honk', self.gf('django.db.models.fields.PositiveIntegerField')(default='0'), keep_default=False)

        # copy data
        for foo in Foo.objects.all():
            # find the right bar here and then ...
            bar.honk = foo.honk
            bar.save()

        # remove old column
        db.delete_column('myapp_foo', 'honk')

Будет ли это работать или не получится, потому что моя (южная замороженная) orm еще не знает о Bar.honk? Или я делаю это неправильно, и есть лучший способ сделать это за одну миграцию?


person Ingmar Hupp    schedule 06.01.2012    source источник


Ответы (4)


Так как этот вопрос принес мне значок перекати-поле, я покопался и попробовал сам. Вот что я узнал.

Нет, вы не можете объединить эти миграции

Поскольку заморозка ORM содержит только ту схему, на которую вы переходите. Таким образом, в приведенном выше примере foo.honk будет недоступен во время переноса данных (цикл for), поскольку он удаляется во время переноса схемы, поэтому он не не в замороженном ORM. Кроме того, вы получите исключение DatabaseError, если попытаетесь получить доступ к данным, потому что столбцы в базе данных еще не соответствуют столбцам модели (например, если вы попытаетесь получить доступ к чему-либо до db.add_column) .

Похоже, что нет простого ярлыка, и выполнение чего-то подобного требует 3 миграции, упомянутых выше.

person Ingmar Hupp    schedule 19.01.2012
comment
Я бы добавил, что это то, что symmetrical = True для переноса данных. Предполагается, что миграция данных имеет непротиворечивую схему до и после, поэтому вы можете использовать orm для доступа к замороженным моделям в forwards и backwards миграции. Поскольку при миграции схемы это не так, никакие данные не должны перемещаться при миграции схемы. Кроме того, не импортируйте имена моделей при переносе данных; вместо этого присвойте этим именам замороженные модели из orm. В руководстве не очень ясно об этом, поэтому я решил включить информацию здесь. - person acjay; 28.01.2013
comment
Я только что объединил такие миграции, используя метод db.execute на юге для манипулирования данными. Также вам нужно использовать db.start_transaction и db.commit_transaction, чтобы разделить изменения схемы и данных на отдельные транзакции. - person clime; 15.04.2013
comment
@clime кажется, что это может быть опасно даже при транзакциях; не существует ли вероятность того, что половина миграции будет зафиксирована в случае, если одна из них выйдет из строя? в этом случае стек миграции может остаться в несогласованном состоянии, и вы не сможете полностью отменить миграцию. - person mpontillo; 22.07.2015

Документации с этой точки зрения не хватает, но если вы измените замороженную часть ORM при миграции, добавив себе отсутствующее поле, тогда она будет доступна: я имею в виду, что во время миграции на юг вы должны использовать замороженную ORM. поскольку в будущем при переносе модель Foo может потерять поле honk.

Я думаю, что если вы измените декларацию замороженного ORM, как показано ниже

models = {
    'app.foo': {
        'Meta': {'object_name': 'Foo'},
        'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
        'foofield': ('django.db.models.fields.CharField', [],{'max_length':666}),
        'honk': ('django.db.models.fields.PositiveIntegerField', [], {}),
    },
    'app.bar': {
        'Meta': {'object_name': 'Bar'},
        'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
        'barfield': ('django.db.models.fields.CharField', [],{'max_length':666}),
        'honk': ('django.db.models.fields.PositiveIntegerField', [], {}),
    },
}

complete_apps = ['app']
symmetrical = True

все будет работать :)

Хитрость заключается в определении поля honk в каждой модели, очевидно, столбец в базе данных должен присутствовать

class Migration(DataMigration):
    def forwards(self, orm):
        # add column
        db.add_column('myapp_bar', 'honk', self.gf('django.db.models.fields.PositiveIntegerField')(default='0'), keep_default=False)

        # copy data
        for foo in Foo.objects.all():
            # find the right bar here
            bar = orm.Bar.objects.get(**whatever)
            bar.honk = foo.honk
            bar.save()

        # remove old column
        db.delete_column('myapp_foo', 'honk')

PS: как отметил @acjohnson55, symmetrical = True действительно важно

person gipi    schedule 08.02.2013

это работает для меня:

def migratedata(orm):
   # copy data, need to lookup model through orm.
   for foo in orm['myapp.foo'].objects.all():
      # find the right bar here and then ...
      bar.honk = foo.honk
      bar.save()

class Migration(SchemaMigration):
   def forwards(self, orm):
      # add column
      db.add_column('myapp_bar', 'honk', self.gf('django.db.models.fields.PositiveIntegerField')(default='0'), keep_default=False)
      # migrate data
      if not db.dry_run:
         migratedata(orm)
      # remove old column
      db.delete_column('myapp_foo', 'honk')

Тем не менее, я не рекомендую это, потому что это легко испортить. Особое внимание следует уделить уникальности и порядку операций (IOW, не переносите данные после удаления соответствующих полей (:)

person dnozay    schedule 28.09.2012

Как упоминал Ингмар, южный ORM замораживается в определенный момент времени, что не позволяет вам получить доступ к столбцам, о которых ORM не знает. Однако на самом деле есть способ обойти это: вам не нужно использовать ORM или вообще какой-либо ORM; вместо этого вы можете выполнять необработанные SQL-запросы

Так, например, вместо

for foo in Foo.objects.all():
    print foo.honk

вы можете сделать что-то вроде:

cursor.execute('SELECT "honk" FROM "myapp_foo"')
for honk, in cursor.fetchall():
    print honk
person Jian    schedule 24.02.2014