Le moyen le plus simple de renommer un modèle en utilisant Django / South?

141

J'ai cherché une réponse à cela sur le site de South, Google et SO, mais je n'ai pas trouvé de moyen simple de le faire.

Je souhaite renommer un modèle Django en utilisant South. Disons que vous avez ce qui suit:

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

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

et vous voulez convertir Foo en Bar, à savoir

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

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

Pour faire simple, j'essaie simplement de changer le nom de Fooen Bar, mais ignorez le foomembre FooTwopour le moment.

Quelle est la façon la plus simple de faire cela en utilisant South?

  1. Je pourrais probablement faire une migration de données, mais cela semble assez compliqué.
  2. Écrivez une migration personnalisée, par exemple db.rename_table('city_citystate', 'geo_citystate'), mais je ne suis pas sûr de savoir comment réparer la clé étrangère dans ce cas.
  3. Un moyen plus simple que vous connaissez?
Vaughnkoch
la source
5
Voir aussi stackoverflow.com/questions/3235995/… pour renommer un champ de modèle plutôt qu'un modèle .
Escargot mécanique du
Solution optimisée pour Django> = 1.8 stackoverflow.com/questions/25091130/...
Chemical Programmer

Réponses:

130

Pour répondre à votre première question, le simple changement de nom de modèle / table est assez simple. Exécutez la commande:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(Mise à jour 2: essayez --autoplutôt que d' --emptyéviter l'avertissement ci-dessous. Merci à @KFB pour le conseil.)

Si vous utilisez une ancienne version de south, vous aurez besoin à la startmigrationplace de schemamigration.

Ensuite, modifiez manuellement le fichier de migration pour qu'il ressemble à ceci:

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

Vous pouvez accomplir cela plus simplement en utilisant l' db_tableoption Meta dans votre classe de modèle. Mais chaque fois que vous faites cela, vous augmentez le poids hérité de votre base de code - avoir des noms de classe différents des noms de table rend votre code plus difficile à comprendre et à maintenir. Je soutiens pleinement les refactorisations simples comme celle-ci dans un souci de clarté.

(mise à jour) Je viens d'essayer cela en production et j'ai reçu un étrange avertissement lorsque je suis allé appliquer la migration. Ça disait:

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'.

J'ai répondu "non" et tout semblait aller bien.

Léopd
la source
3
J'ai pu éviter le message d'erreur de Leopd en créant la migration de schéma en utilisant --auto au lieu de --empty. J'ai ensuite édité le fichier de migration, en changeant la suppression / création des tables en un appel db.rename_table (). Cela semble avoir très bien fonctionné.
KFB
4
J'ai utilisé cette technique le 02/09/2011 sans obtenir d'erreur. Peut-être qu'une version plus récente de South a résolu le problème des erreurs.
Chip Tol
1
Merci de garder ceci à jour! La réponse de Jian ci-dessous dit qu'il est important de conserver les appels "send_create_signal", avez-vous des connaissances à ce sujet? Si vous êtes d'accord, ce serait bien de mettre à jour votre exemple de migration.
mrooney
5
Attention, cela ne renommera pas les index de cette table. Si vous créez une nouvelle table à l'avenir avec le même nom que l'ancienne table, vous pouvez obtenir des erreurs de conflits de noms d'index. Nous utilisions cette technique, mais à partir de maintenant, nous allons créer explicitement la nouvelle table, migrer les données, puis supprimer l'ancienne table.
Jeremy Banks
3
Les noms de colonne dans les tables générées automatiquement, telles que les tables M2M vers le modèle d'origine, ne sont pas non plus migrés par cette méthode.
spookylukey
66

Apportez les modifications models.pyet exécutez

./manage.py schemamigration --auto myapp

Lorsque vous inspectez le fichier de migration, vous verrez qu'il supprime une table et en crée une nouvelle

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):
        ...

Ce n'est pas tout à fait ce que vous voulez. Au lieu de cela, modifiez la migration pour qu'elle ressemble à:

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

En l'absence de updatedéclaration, l' db.send_create_signalappel créera un nouveau ContentTypeavec le nouveau nom de modèle. Mais il vaut mieux juste updatele que ContentTypevous avez déjà au cas où il y aurait des objets de base de données pointant vers lui (par exemple, via a GenericForeignKey).

De plus, si vous avez renommé certaines colonnes qui sont des clés étrangères du modèle renommé, n'oubliez pas de

db.rename_column(myapp_model, foo_id, bar_id)
Jian
la source
2
J'obtiens l'erreur, KeyError: "Le modèle 'contenttype' de l'application 'contenttypes' n'est pas disponible dans cette migration." De plus, j'ai une table django_content_type, mais pas une table contenttypes. (Django 1.6)
Seth
2
@Seth J'ai contourné ce problème en mettant à jour les modèles ContentType dans une migration de données distincte et en ajoutant le contenttypes.ContentTypemodèle aux modèles gelés en utilisant l' --frozenindicateur to ./manage.py datamigration. Par exemple: ./manage.py datamigration --frozen contenttypes myapp update_contenttypes. Puis modifiez myapp_migrations / NNNN_update_contenttypes.py avec le code de mise à jour du type de contenu comme spécifié ci-dessus.
Geoffrey Hing
@GeoffreyHing Je pense que le paramètre est gelé non gelé. south.readthedocs.io/en/latest/ormfreezing.html Mais merci beaucoup pour votre aide, cela a été vraiment utile.
ccsakuweb
5

Le Sud ne peut pas le faire lui-même - comment sait-il que cela Barreprésente ce qui était Fooautrefois? C'est le genre de chose pour laquelle j'écrirais une migration personnalisée. Vous pouvez modifier votre ForeignKeycode comme vous l'avez fait ci-dessus, puis il vous suffit de renommer les champs et les tables appropriés, ce que vous pouvez faire comme vous le souhaitez.

Enfin, avez-vous vraiment besoin de faire cela? Je n'ai pas encore besoin de renommer les modèles - les noms de modèles ne sont qu'un détail d'implémentation - en particulier compte tenu de la disponibilité de l' verbose_nameoption Meta.

Dominic Rodger
la source
7
Vous pouvez également renommer le modèle en code mais utiliser l' db_tableoption Meta pour conserver le même nom de table de base de données.
Daniel Roseman
@Daniel - savez-vous si db_tableest utilisé pour dériver des noms de clés étrangères?
Dominic Rodger
je crois que c'est. Si vous modifiez le nom du modèle et définissez db_table, tout devrait toujours fonctionner comme prévu.
Davor Lucic
1
@DanielRoseman c'est la meilleure solution dans tout le fil!
joerick
-1

J'ai suivi la solution de Leopd ci-dessus. Mais cela n'a pas changé les noms des modèles. Je l'ai changé manuellement dans le code (également dans les modèles associés où cela s'appelle FK). Et fait une autre migration vers le sud, mais avec l'option --fake. Cela rend les noms de modèle et les noms de table identiques.

A peine réalisé, on peut commencer par changer les noms de modèle, puis éditer le fichier de migrations avant de les appliquer. Beaucoup plus propre.

Gowthaman
la source