Estratégia de migração do Django para renomear um modelo e campos de relacionamento
-
02-01-2020 - |
Pergunta
Estou planejando renomear vários modelos em um projeto Django existente, onde existem muitos outros modelos que possuem relacionamentos de chave estrangeira com os modelos que gostaria de renomear.Tenho quase certeza de que isso exigirá várias migrações, mas não tenho certeza do procedimento exato.
Digamos que eu comece com os seguintes modelos em um aplicativo Django chamado myapp
:
class Foo(models.Model):
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
class AnotherModel(models.Model):
foo = models.ForeignKey(Foo)
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
foo = models.ForeignKey(Foo)
is_ridonkulous = models.BooleanField()
Eu quero renomear o Foo
modelo porque o nome realmente não faz sentido e está causando confusão no código, e Bar
daria um nome muito mais claro.
Pelo que li na documentação de desenvolvimento do Django, estou assumindo a seguinte estratégia de migração:
Passo 1
Modificar models.py
:
class Bar(models.Model): # <-- changed model name
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
class AnotherModel(models.Model):
foo = models.ForeignKey(Bar) # <-- changed relation, but not field name
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
foo = models.ForeignKey(Bar) # <-- changed relation, but not field name
is_ridonkulous = models.BooleanField()
Note o AnotherModel
nome do campo para foo
não muda, mas a relação é atualizada para o Bar
modelo.Meu raciocínio é que eu não deveria mudar muito de uma vez e que se eu mudasse o nome deste campo para bar
Eu correria o risco de perder os dados dessa coluna.
Passo 2
Crie uma migração vazia:
python manage.py makemigrations --empty myapp
etapa 3
Edite o Migration
class no arquivo de migração criado na etapa 2 para adicionar o RenameModel
operação para a lista de operações:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.RenameModel('Foo', 'Bar')
]
Passo 4
Aplique a migração:
python manage.py migrate
Etapa 5
Edite os nomes dos campos relacionados em models.py
:
class Bar(models.Model):
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
class AnotherModel(models.Model):
bar = models.ForeignKey(Bar) # <-- changed field name
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
bar = models.ForeignKey(Bar) # <-- changed field name
is_ridonkulous = models.BooleanField()
Etapa 6
Crie outra migração vazia:
python manage.py makemigrations --empty myapp
Etapa 7
Edite o Migration
class no arquivo de migração criado na etapa 6 para adicionar o RenameField
operação(ões) para qualquer nome de campo relacionado à lista de operações:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0002_rename_fields'), # <-- is this okay?
]
operations = [
migrations.RenameField('AnotherModel', 'foo', 'bar'),
migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]
Etapa 8
Aplique a 2ª migração:
python manage.py migrate
Além de atualizar o restante do código (visualizações, formulários, etc.) para refletir os novos nomes de variáveis, é basicamente assim que a nova funcionalidade de migração funcionaria?
Além disso, isso parece ser uma série de etapas.As operações de migração podem ser condensadas de alguma forma?
Obrigado!
Solução
Então, quando tentei isso, parece que você pode condensar as etapas 3 a 7:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.RenameModel('Foo', 'Bar'),
migrations.RenameField('AnotherModel', 'foo', 'bar'),
migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]
Você pode receber alguns erros se não atualizar os nomes de onde foram importados, por exemplo.admin.py e arquivos de migração ainda mais antigos (!).
Atualizar:Como César menciona, versões mais recentes do Django geralmente são capazes de detectar e perguntar se um modelo foi renomeado.Então tente manage.py makemigrations
primeiro e depois verifique o arquivo de migração.
Outras dicas
No início, pensei que o método de Fiver funcionasse para mim porque a migração funcionou bem até a etapa 4.No entanto, as alterações implícitas de 'ForeignKeyField(Foo)' para 'ForeignKeyField(Bar)' não foram relacionadas em nenhuma migração.É por isso que a migração falhou quando quis renomear os campos de relacionamento (etapas 5 a 8).Isso pode ser devido ao fato de que meu 'AnotherModel' e 'YetAnotherModel' são despachados em outros aplicativos no meu caso.
Então consegui renomear meus modelos e campos de relacionamento seguindo as etapas abaixo:
Eu adaptei o método de esse e particularmente o truque do otranzer.
Então, como Fiver, digamos que temos em meu aplicativo:
class Foo(models.Model):
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
E em meu outro aplicativo:
class AnotherModel(models.Model):
foo = models.ForeignKey(Foo)
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
foo = models.ForeignKey(Foo)
is_ridonkulous = models.BooleanField()
Passo 1:
Transforme cada OneToOneField(Foo) ou ForeignKeyField(Foo) em IntegerField().(Isso manterá o id do objeto Foo relacionado como valor do campo inteiro).
class AnotherModel(models.Model):
foo = models.IntegerField()
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
foo = models.IntegerField()
is_ridonkulous = models.BooleanField()
Então
python manage.py makemigrations
python manage.py migrate
Passo 2:(Como a etapa 2-4 do Fiver)
Alterar o nome do modelo
class Bar(models.Model): # <-- changed model name
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
Crie uma migração vazia:
python manage.py makemigrations --empty myapp
Em seguida, edite como:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.RenameModel('Foo', 'Bar')
]
Eventualmente
python manage.py migrate
Etapa 3:
Transforme seu IntegerField() em seu ForeignKeyField ou OneToOneField anterior, mas com o novo modelo de barra.(O campo inteiro anterior estava armazenando o id, então o Django entende isso e restabelece a conexão, o que é legal.)
class AnotherModel(models.Model):
foo = models.ForeignKey(Bar)
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
foo = models.ForeignKey(Bar)
is_ridonkulous = models.BooleanField()
Então faça:
python manage.py makemigrations
Muito importante, nesta etapa você deve modificar cada nova migração e adicionar a dependência nas migrações RenameModel Foo-> Bar.Portanto, se AnotherModel e YetAnotherModel estiverem em myotherapp, a migração criada em myotherapp deverá ser semelhante a esta:
class Migration(migrations.Migration):
dependencies = [
('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
]
operations = [
migrations.AlterField(
model_name='anothermodel',
name='foo',
field=models.ForeignKey(to='myapp.Bar'),
),
migrations.AlterField(
model_name='yetanothermodel',
name='foo',
field=models.ForeignKey(to='myapp.Bar')
),
]
Então
python manage.py migrate
Passo 4:
Eventualmente você pode renomear seus campos
class AnotherModel(models.Model):
bar = models.ForeignKey(Bar) <------- Renamed fields
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
bar = models.ForeignKey(Bar) <------- Renamed fields
is_ridonkulous = models.BooleanField()
e depois renomeie automaticamente
python manage.py makemigrations
(Django deve perguntar se você realmente renomeou o nome do modelo, diga sim)
python manage.py migrate
E é isso!
Isso funciona no Django1.8
Eu precisava fazer a mesma coisa.Mudei o modelo de uma vez (ou seja, etapa 1 e etapa 5 juntas).Em seguida, criei uma migração de esquema, mas editei-a para ficar assim:
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('Foo','Bar')
def backwards(self, orm):
db.rename_table('Bar','Foo')
Isso funcionou perfeitamente.Todos os meus dados existentes apareceram, todas as outras tabelas referenciadas Bar bem.
daqui: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/
Para o Django 1.10, consegui alterar dois nomes de classes de modelo (incluindo ForeignKey e com dados) simplesmente executando Makemigrations e depois Migrate para o aplicativo.Para a etapa Makemigrations, tive que confirmar que queria alterar os nomes das tabelas.O Migrate alterou os nomes das tabelas sem problemas.
Em seguida, alterei o nome do campo ForeignKey para corresponder e novamente fui solicitado pela Makemigrations para confirmar que queria alterar o nome.Migrar do que fazer a alteração.
Então fiz isso em duas etapas, sem nenhuma edição especial de arquivo.Recebi erros no início porque esqueci de alterar o arquivo admin.py, conforme mencionado por @wasibigeek.
Eu também enfrentei o problema conforme descrito por v.thorey e descobri que sua abordagem é muito útil, mas pode ser condensada em menos etapas que são, na verdade, as etapas 5 a 8, como Fiver descreveu, sem as etapas 1 a 4, exceto que a etapa 7 precisa ser alterada como meu abaixo da etapa 3.As etapas gerais são as seguintes:
Passo 1:Edite os nomes dos campos relacionados em models.py
class Bar(models.Model):
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
class AnotherModel(models.Model):
bar = models.ForeignKey(Bar) # <-- changed field name
is_awesome = models.BooleanField()
class YetAnotherModel(models.Model):
bar = models.ForeignKey(Bar) # <-- changed field name
is_ridonkulous = models.BooleanField()
Passo 2:Crie uma migração vazia
python manage.py makemigrations --empty myapp
Etapa 3:Edite a classe Migration no arquivo de migração criado na Etapa 2
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='AnotherModel',
name='foo',
field=models.IntegerField(),
),
migrations.AlterField(
model_name='YetAnotherModel',
name='foo',
field=models.IntegerField(),
),
migrations.RenameModel('Foo', 'Bar'),
migrations.AlterField(
model_name='AnotherModel',
name='foo',
field=models.ForeignKey(to='myapp.Bar'),
),
migrations.AlterField(
model_name='YetAnotherModel',
name='foo',
field=models.ForeignKey(to='myapp.Bar'),
),
migrations.RenameField('AnotherModel', 'foo', 'bar'),
migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]
Passo 4:Aplicar a migração
python manage.py migrate
Feito
P.S.Eu tentei essa abordagem no Django 1.9
Estou usando Django versão 1.9.4
Eu segui os seguintes passos: -
Acabei de renomear o modelo Oldname para o newName Run python manage.py makemigrations
.Ele vai te pedirDid you rename the appname.oldName model to NewName? [y/N]
selecione Y
Correr python manage.py migrate
e ele vai te pedir
Os seguintes tipos de conteúdo estão obsoletos e precisam ser excluídos:
appname | oldName
appname | NewName
Quaisquer objetos relacionados a esses tipos de conteúdo por uma chave estrangeira também serão excluídos.Tem certeza de que deseja excluir esses tipos de conteúdo?Se não tiver certeza, responda 'não'.
Type 'yes' to continue, or 'no' to cancel: Select No
Ele renomeia e migra todos os dados existentes para uma nova tabela nomeada para mim.
Infelizmente, encontrei problemas (cada Django 1.x) com a migração de renomeação que deixa nomes de tabelas antigas no banco de dados.
Django nem tenta nada na mesa antiga, apenas renomeia seu próprio modelo.O mesmo problema com chaves estrangeiras e índices em geral - as alterações não são rastreadas corretamente pelo Django.
A solução mais simples (solução alternativa):
class Foo(models.Model):
name = models.CharField(unique=True, max_length=32)
...
Bar = Foo # and use Bar only
A solução real (uma maneira fácil de mudar todos os índices, restrições, gatilhos, nomes, etc. em 2 compromissos, mas sim para menor tabelas):
cometer A:
- crie o mesmo modelo como o antigo
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
...
class Bar(model.Model):
...
- mudar o código para funcionar com o novo modelo
Bar
apenas.(incluindo todas as relações no esquema)
Na migração prepare-se RunPython
, que copiam dados de foo para bar (incluindo id
de Foo)
- otimização opcional (se necessário para tabelas maiores)
cometer B: (sem pressa, faça isso quando uma equipe inteira for migrada)
- queda segura do modelo antigo
Foo
limpeza adicional:
- squash nas migrações
bug no Django:
Eu precisava renomear algumas tabelas.Mas apenas uma renomeação de modelo foi notada pelo Django.Isso aconteceu porque o Django itera sobre modelos adicionados e depois removidos.Para cada par verifica se eles são do mesmo aplicativo e têm campos idênticos.Apenas uma tabela não tinha chaves estrangeiras para tabelas a serem renomeadas (as chaves estrangeiras contêm o nome da classe do modelo, como você se lembra).Ou seja, apenas uma tabela não teve alterações de campo.É por isso que foi notado.
Então, a solução é renomear uma tabela por vez, alterando o nome da classe do modelo em models.py
, possivelmente views.py
, e fazendo uma migração.Depois disso, inspecione seu código em busca de outras referências (nomes de classes de modelo, nomes relacionados (consulta), nomes de variáveis).Faça uma migração, se necessário.Em seguida, combine opcionalmente todas essas migrações em uma (certifique-se de copiar também as importações).
Eu faria palavras de @ceasaro, minhas em seu comentário sobre isso responder.
Versões mais recentes do Django podem detectar alterações e perguntar o que foi feito.Eu também acrescentaria que o Django pode misturar a ordem de execução de alguns comandos de migração.
Seria sensato aplicar pequenas alterações e executar makemigrations
e migrate
e se o erro ocorrer, o arquivo de migração poderá ser editado.
A ordem de execução de algumas linhas pode ser alterada para evitar erros.
Só queria confirmar e acrescentar o comentário do Ceasaro.O Django 2.0 parece fazer isso automaticamente agora.
Estou no Jango 2.2.1, tudo o que tive que fazer foi renomear o modelo e executar makemigrations.
Aqui ele pergunta se eu renomeei a classe específica de A para B, escolhi sim e executei a migração e tudo parece funcionar.
Observe que não renomeei o nome do modelo antigo em nenhum arquivo dentro da pasta project/migrations.
Se você estiver usando um bom IDE como o PyCharm, você pode clicar com o botão direito no nome do modelo e refatorar -> renomear.Isso evita o trabalho de passar por todo o código que faz referência ao modelo.Em seguida, execute makemigrations e migre.Django 2+ simplesmente confirmará a mudança de nome.
Atualizei o Django da versão 10 para a versão 11:
sudo pip install -U Django
(-U
para "upgrade") e resolveu o problema.