Pergunta

Eu tenho um aplicativo Django com quatro modelos-lo. Percebo agora que um desses modelos deve estar em um aplicativo separado. Eu tenho sul instalado para migrações, mas eu não acho que isso é algo que ele pode manipular automaticamente. Como posso migrar um dos modelos fora do aplicativo antigo para um novo?

Além disso, mantenha em mente que eu vou precisar que este é um processo repetitivo, para que eu possa migrar o sistema de produção e tal.

Foi útil?

Solução

Como migrar usando sul.

Vamos dizer que temos dois aplicativos: comum e específica:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

Agora, queremos mover modelo common.models.cat de aplicativo específico (justamente para specific.models.cat). Primeiro faça as mudanças no código-fonte e, em seguida, execute:

$ python manage.py schemamigration specific create_cat --auto
 + Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
 - Deleted model 'common.cat'

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0002_create_cat.py
|   |   `-- 0003_drop_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0002_create_dog.py
    |   `-- 0003_create_cat.py
    `-- models.py

Agora precisamos editar os dois arquivos de migração:

#0003_create_cat: replace existing forward and backward code
#to use just one sentence:

def forwards(self, orm):
    db.rename_table('common_cat', 'specific_cat') 

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='common',
            model='cat',
        ).update(app_label='specific')

def backwards(self, orm):
    db.rename_table('specific_cat', 'common_cat')

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='specific',
            model='cat',
        ).update(app_label='common')

#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:

depends_on = (
    ('specific', '0003_create_cat'),
)
def forwards(self, orm):
    pass
def backwards(self, orm):
    pass

Agora, ambos os aplicativos migrações estão cientes da mudança e vida suga um pouco menos :-) A definição dessa relação entre migrações é a chave do sucesso. Agora, se você faz:

python manage.py migrate common
 > specific: 0003_create_cat
 > common: 0003_drop_cat

vai fazer as duas coisas migração, e

python manage.py migrate specific 0002_create_dog
 < common: 0003_drop_cat
 < specific: 0003_create_cat

migrará as coisas.

Observe que para o upgrade do esquema I utilizado aplicativo comum e para a desclassificação, eu usei aplicativo específico. Isso porque como a dependência aqui funciona.

Outras dicas

Para construir sobre potr Czachur 's resposta , situações que envolvem ForeignKeys são mais complicadas e deve ser tratado de forma ligeiramente diferente.

(O exemplo a seguir baseia-se na common e aplicativos specific referiu-se ao na resposta atual).

# common/models.py

class Cat(models.Model):
    # ...

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

então mudar para

# common/models.py

from specific.models import Cat

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

# specific/models.py

class Cat(models.Model):
    # ...

Running

./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial

geraria o seguinte as migrações (estou intencionalmente ignorando Django ContentType muda-ver a resposta referenciado anteriormente para saber como lidar com isso):

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_table('common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.create_table('common_cat', (
            # ...
        ))
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.create_table('specific_cat', (
            # ...
        ))

    def backwards(self, orm):
        db.delete_table('specific_cat')

Como você pode ver, o FK deve ser alterada para fazer referência a nova tabela. Precisamos adicionar uma dependência para que possamos saber a ordem em que as migrações serão aplicados (e, portanto, que a tabela vai existir antes de tentar adicionar um FK a ele), mas também precisamos ter certeza de rolar para trás funciona também porque < forte> a dependência aplica-se no sentido inverso .

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):

    depends_on = (
        ('specific', '0004_auto__add_cat'),
    )

    def forwards(self, orm):
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat')

    def backwards(self, orm):
        pass

Por Sul documentação , depends_on garantirá que 0004_auto__add_cat corridas antes 0009_auto__del_cat para trás ao migrar para a frente , mas na ordem inversa ao migrar . Se deixamos db.rename_table('specific_cat', 'common_cat') na reversão specific, a reversão common iria falhar ao tentar migrar o ForeignKey porque a tabela tabela de referência não existiria.

Esperamos que este está mais perto de uma situação de "mundo real" do que as soluções e alguém existentes vai encontrar este útil. Felicidades!

Os modelos são não muito firmemente acoplado a apps, tão comovente é bastante simples. Django usa o nome do aplicativo no nome da tabela de banco de dados, por isso, se você deseja mover o aplicativo você pode renomear a tabela de banco de dados por meio de uma declaração ALTER TABLE SQL, ou - ainda mais simples - basta usar o db_table parâmetro na classe Meta do seu modelo para se referir ao nome antigo.

Se você já usou ContentTypes ou relações genéricas em qualquer lugar em seu código até agora, você provavelmente vai querer mudar o nome do app_label do apontando contenttype no modelo que está se movendo, para que as relações existentes são preservados.

Claro que, se você não tem quaisquer dados para preservar, a melhor coisa a fazer é eliminar as tabelas de banco de dados completamente e ./manage.py syncdb prazo novamente.

Aqui está mais uma correção para excelente solução do potr. Adicione o seguinte ao específica / 0003_create_cat

depends_on = (
    ('common', '0002_create_cat'),
)

A menos que essa dependência é definida Sul não será garantia de que existe tabela de common_cat no momento em que específica / 0003_create_cat é executado, jogando um erro django.db.utils.OperationalError: no such table: common_cat em você.

South corre migrações em lexicographical fim menos dependência é explicitamente conjunto. Desde common vem antes specific todas as migrações do common iria se executar antes de renomear a tabela, por isso provavelmente não reproduzir no exemplo original mostrado por potr. Mas se você renomear common para app2 e specific para app1 você vai executar para esse problema.

O processo que eu atualmente liquidada em desde que eu estive de volta aqui algumas vezes e decidiu formalizar-lo.

Esta foi originalmente construído em do potr Czachur resposta e da Matt Briançon resposta , utilizando Sul 0.8.4

Passo 1. Descubra criança relações de chave estrangeira

# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
 <RelatedObject: identity:microchip related to cat>]

Portanto, neste caso prolongado, descobrimos um outro modelo relacionadas, como:

# Inside the "identity" app...
class Microchip(models.Model):

    # In reality we'd probably want a ForeignKey, but to show the OneToOneField
    identifies = models.OneToOneField(Cat)

    ...

Passo 2. Criar migrações

# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto

# Drop the old model
python manage.py schemamigration common drop_cat --auto

# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto

Passo 3. control Fonte: Confirmar alterações até agora

.

Faz-se um processo mais repetível, se você tiver conflitos de mesclagem como companheiros de equipe escrita migrações sobre os aplicativos atualizados.

Passo 4. Adicione dependências entre as migrações.

Basicamente create_kittycat depende do estado atual de tudo, e tudo então depende create_kittycat.

# create_kittycat
class Migration(SchemaMigration):

    depends_on = (
        # Original model location
        ('common', 'the_one_before_drop_cat'),

        # Foreign keys to models not in original location
        ('identity', 'the_one_before_update_microchip_fk'),
    )
    ...


# drop_cat
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...


# update_microchip_fk
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...

Passo 5. A alteração da tabela de renomeação queremos fazer.

# create_kittycat
class Migration(SchemaMigration):

    ...

    # Hopefully for create_kittycat you only need to change the following
    # 4 strings to go forward cleanly... backwards will need a bit more work.
    old_app = 'common'
    old_model = 'cat'
    new_app = 'specific'
    new_model = 'kittycat'

    # You may also wish to update the ContentType.name,
    # personally, I don't know what its for and
    # haven't seen any side effects from skipping it.

    def forwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.old_app, self.old_model),
            '%s_%s' % (self.new_app, self.new_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.old_app,
                model=self.old_model,
            ).update(
                app_label=self.new_app,
                model=self.new_model,
            )

        # Going forwards, should be no problem just updating child foreign keys
        # with the --auto in the other new South migrations

    def backwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.new_app, self.new_model),
            '%s_%s' % (self.old_app, self.old_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.new_app,
                model=self.new_model,
            ).update(
                app_label=self.old_app,
                model=self.old_model,
            )

        # Going backwards, you probably should copy the ForeignKey
        # db.alter_column() changes from the other new migrations in here
        # so they run in the correct order.
        #
        # Test it! See Step 6 for more details if you need to go backwards.
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))


# drop_cat
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Remove the db.delete_table(), if you don't at Step 7 you'll likely get
        # "django.db.utils.ProgrammingError: table "common_cat" does not exist"

        # Leave existing db.alter_column() statements here
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass


# update_microchip_fk
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Leave existing db.alter_column() statements here
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass

Passo 6. Só se você precisa para trás () ao trabalho e obter um KeyError correndo para trás.

# the_one_before_create_kittycat
class Migration(SchemaMigration):

    # You many also need to add more models to South's FakeORM if you run into
    # more KeyErrors, the trade-off chosen was to make going forward as easy as
    # possible, as that's what you'll probably want to do once in QA and once in
    # production, rather than running the following many times:
    #
    # python manage.py migrate specific <the_one_before_create_kittycat>

    models = {
        ...
        # Copied from 'identity' app, 'update_microchip_fk' migration
        u'identity.microchip': {
            'Meta': {'object_name': 'Microchip'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
        },
        ...
    }

Passo 7. Teste-lo - o que funciona para mim pode não ser suficiente para a sua situação de vida real:)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>

Então, usando a resposta original a partir @Potr acima não funcionou para mim no Sul 0.8.1 e 1.5.1 Django. Estou postando o que fez trabalho para me seguir na esperança de que seja útil para outras pessoas.

from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat') 

        if not db.dry_run:
             db.execute(
                "update django_content_type set app_label = 'specific' where "
                " app_label = 'common' and model = 'cat';")

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
            db.execute(
                "update django_content_type set app_label = 'common' where "
                " app_label = 'specific' and model = 'cat';")

Eu vou dar uma versão mais explícita de uma das coisas que Daniel Roseman sugeridas em sua resposta ...

Se você acabou de mudar o db_table atributo Meta do modelo que você moveu para apontar para o nome da tabela existente (em vez do novo nome Django lhe daria se você caiu e fez um syncdb), então você pode evitar complicadas migrações Sul. por exemplo:

Original:

# app1/models.py
class MyModel(models.Model):
    ...

Depois de se mudar:

# app2/models.py
class MyModel(models.Model):
    class Meta:
        db_table = "app1_mymodel"

Agora você só precisa fazer uma migração de dados para atualizar o app_label para MyModel na tabela de django_content_type e você deve ser bom para ir ...

Executar ./manage.py datamigration django update_content_type então editar o arquivo que Sul cria para você:

def forwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
    moved.app_label = 'app2'
    moved.save()

def backwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
    moved.app_label = 'app1'
    moved.save()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top