A maneira mais fácil de renomear um modelo usando Django / South?

141

Eu estava procurando uma resposta para isso no site da South, Google e SO, mas não consegui encontrar uma maneira simples de fazer isso.

Eu quero renomear um modelo Django usando South. Digamos que você tenha o seguinte:

class Foo(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Foo)

e você deseja converter Foo para Bar, a saber

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

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Bar)

Para simplificar, estou apenas tentando alterar o nome de Foopara Bar, mas ignore o foomembro FooTwopor enquanto.

Qual é a maneira mais fácil de fazer isso usando o Sul?

  1. Provavelmente eu poderia fazer uma migração de dados, mas isso parece bastante envolvido.
  2. Escreva uma migração personalizada, por exemplo db.rename_table('city_citystate', 'geo_citystate'), mas não tenho certeza de como corrigir a chave estrangeira neste caso.
  3. Uma maneira mais fácil que você conhece?
vaughnkoch
fonte
5
Consulte também stackoverflow.com/questions/3235995/… para renomear um campo de modelo em vez de um modelo .
Caracol mecânico
Solução otimizada para Django> = 1.8 stackoverflow.com/questions/25091130/…
Programador químico

Respostas:

130

Para responder à sua primeira pergunta, a simples renomeação de modelo / tabela é bastante direta. Execute o comando:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(Atualização 2: tente em --autovez de --emptyevitar o aviso abaixo. Agradecemos a @KFB pela dica.)

Se você estiver usando uma versão mais antiga do sul, precisará em startmigrationvez de schemamigration.

Em seguida, edite manualmente o arquivo de migração para ficar assim:

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('yourapp_foo', 'yourapp_bar')


    def backwards(self, orm):
        db.rename_table('yourapp_bar','yourapp_foo')   

Você pode fazer isso mais simplesmente usando a db_tableopção Meta na sua classe de modelo. Mas sempre que você faz isso, aumenta o peso herdado da sua base de código - ter nomes de classes diferentes dos nomes de tabelas torna seu código mais difícil de entender e manter. Apoio totalmente a refatoração simples como essa por uma questão de clareza.

(atualização) Eu apenas tentei isso em produção e recebi um aviso estranho quando fui aplicar a migração. Dizia:

The following content types are stale and need to be deleted:

    yourapp | foo

Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.

Eu respondi "não" e tudo parecia estar bem.

Leopd
fonte
3
Consegui evitar a mensagem de erro do Leopd criando a migração do esquema usando --auto em vez de --vazio. Em seguida, editei o arquivo de migração, alterando a exclusão / criação das tabelas em uma chamada db.rename_table (). Isso parece ter funcionado muito bem.
KFB
4
Eu usei essa técnica em 2/09/2011 sem obter erros. Talvez uma versão mais recente do Sul tenha resolvido o problema com os erros.
Chip Tol
1
Obrigado por manter isso atualizado! A resposta de Jian abaixo diz que é importante manter as chamadas "send_create_signal", você tem algum conhecimento sobre isso? Se você concordar, seria ótimo atualizar seu exemplo de migração.
21412 mroney
5
Cuidado para que isso não renomeie índices nessa tabela. Se você criar uma nova tabela no futuro com o mesmo nome da tabela antiga, poderá obter erros ao colidir nomes de índice. Estávamos usando essa técnica, mas a partir de agora vamos criar explicitamente a nova tabela, migrar os dados e excluir a tabela antiga.
Jeremy Banks
3
Os nomes de colunas em tabelas geradas automaticamente, como tabelas M2M para o modelo original, também não são migrados por esse método.
Spookylukey
66

Faça as alterações models.pye execute

./manage.py schemamigration --auto myapp

Ao inspecionar o arquivo de migração, você verá que ele exclui uma tabela e cria uma nova

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Deleting model 'Foo'                                                                                                                      
        db.delete_table('myapp_foo')

        # Adding model 'Bar'                                                                                                                        
        db.create_table('myapp_bar', (
        ...
        ))
        db.send_create_signal('myapp', ['Bar'])

    def backwards(self, orm):
        ...

Isto não é exatamente o que você deseja. Em vez disso, edite a migração para que se pareça com:

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Renaming model from 'Foo' to 'Bar'                                                                                                                      
        db.rename_table('myapp_foo', 'myapp_bar')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(
                app_label='myapp', model='foo').update(model='bar')

    def backwards(self, orm):
        # Renaming model from 'Bar' to 'Foo'                                                                                                                      
        db.rename_table('myapp_bar', 'myapp_foo')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(app_label='myapp', model='bar').update(model='foo')

Na ausência da updateinstrução, a db.send_create_signalchamada criará um novo ContentTypecom o novo nome do modelo. Mas é melhor apenas updateo que ContentTypevocê já possui, caso haja objetos de banco de dados apontando para ele (por exemplo, via a GenericForeignKey).

Além disso, se você renomeou algumas colunas que são chaves estrangeiras para o modelo renomeado, não se esqueça de

db.rename_column(myapp_model, foo_id, bar_id)
Jian
fonte
2
Recebo o erro KeyError: "O modelo 'contenttype' do aplicativo 'contenttypes' não está disponível nesta migração." Além disso, tenho uma tabela django_content_type, mas não uma tabela contenttypes. (Django 1.6)
Seth
2
@Seth Eu resolvi isso fazendo a atualização dos modelos ContentType em uma migração de dados separada e adicionando o contenttypes.ContentTypemodelo aos modelos congelados usando o --frozensinalizador para ./manage.py datamigration. Por exemplo: ./manage.py datamigration --frozen contenttypes myapp update_contenttypes. Em seguida, edite myapp_migrations / NNNN_update_contenttypes.py com o código de atualização do tipo de conteúdo, conforme especificado acima.
Geoffrey Hing
@GeoffreyHing Acho que o parâmetro é congelar sem congelar. south.readthedocs.io/en/latest/ormfreezing.html Mas muito obrigado por sua ajuda, foi realmente útil.
precisa saber é o seguinte
5

O Sul não pode fazer isso sozinho - como ele sabe que Barrepresenta o que Foocostumava fazer? É para esse tipo de coisa que eu escreveria uma migração personalizada. Você pode alterar o seu ForeignKeycódigo como você fez acima e, em seguida, é apenas um caso de renomear os campos e tabelas apropriados, que você pode fazer da maneira que desejar.

Finalmente, você realmente precisa fazer isso? Eu ainda preciso renomear modelos - os nomes dos modelos são apenas um detalhe da implementação - particularmente dada a disponibilidade da verbose_nameopção Meta.

Dominic Rodger
fonte
7
Como alternativa, renomeie o modelo no código, mas use a db_tableopção Meta para manter o mesmo nome da tabela do banco de dados.
Daniel Roseman
@ Daniel - você sabe se db_tableé usado para derivar nomes de chaves estrangeiras?
Dominic Rodger
eu acredito que é. Se você alterar o nome do modelo e definir db_table, tudo ainda funcionará conforme o esperado.
Davor Lucic
1
@DanielRoseman, esta é a melhor solução em todo o segmento!
11134 joerick
-1

Eu segui a solução de Leopd acima. Mas isso não mudou os nomes dos modelos. Eu mudei manualmente no código (também em modelos relacionados, onde isso é chamado de FK). E fez outra migração para o sul, mas com a opção --fake. Isso faz com que os nomes dos modelos e da tabela sejam os mesmos.

Acabado de perceber, é possível primeiro começar com a alteração dos nomes dos modelos e editar o arquivo de migrações antes de aplicá-los. Muito mais limpo.

gowthaman
fonte