web-dev-qa-db-fra.com

Stratégie de migration Django pour renommer un modèle et des champs de relation

Je prévois de renommer plusieurs modèles dans un projet Django existant dans lequel de nombreux autres modèles ont des relations de clé étrangères avec les modèles que je souhaite renommer. Je suis presque certain que cela nécessitera plusieurs migrations, mais je ne suis pas sûr de la procédure exacte.

Supposons que je commence avec les modèles suivants au sein d'une application Django appelée 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()

Je souhaite renommer le modèle Foo car le nom n'a pas vraiment de sens et est source de confusion dans le code. Bar rendrait le nom beaucoup plus clair. 

D'après ce que j'ai lu dans la documentation de développement de Django, j'assume la stratégie de migration suivante:

Étape 1

Modifier 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()

Notez que le nom du champ AnotherModel pour foo ne change pas, mais la relation est mise à jour vers le modèle Bar. Mon raisonnement est que je ne devrais pas trop changer en même temps et que si je changeais le nom de ce champ en bar, je risquerais de perdre les données de cette colonne.

Étape 2

Créez une migration vide:

python manage.py makemigrations --empty myapp

Étape 3

Modifiez la classe Migration dans le fichier de migration créé à l'étape 2 pour ajouter l'opération RenameModel à la liste des opérations:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Étape 4

Appliquer la migration:

python manage.py migrate

Étape 5

Editez les noms de champs associés dans 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()

Étape 6

Créez une autre migration vide:

python manage.py makemigrations --empty myapp

Étape 7

Modifiez la classe Migration dans le fichier de migration créé à l'étape 6 pour ajouter la ou les opérations RenameField de tous les noms de champ associés à la liste des opérations:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Étape 8

Appliquez la 2ème migration:

python manage.py migrate

Outre la mise à jour du reste du code (vues, formulaires, etc.) afin de refléter les nouveaux noms de variable, s'agit-il en gros de la manière dont la nouvelle fonctionnalité de migration fonctionnerait?

En outre, cela semble être beaucoup d'étapes. Les opérations de migration peuvent-elles être condensées d'une manière ou d'une autre?

Merci!

117
Fiver

Donc, lorsque j'ai essayé cela, il semble que vous puissiez condenser les étapes 3 à 7:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'), 
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar'),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Vous risquez d’obtenir des erreurs si vous ne mettez pas à jour les noms là où il est importé, par exemple. admin.py et même des fichiers de migration plus anciens (!).

Update: Comme ceasaro mentionne, les versions plus récentes de Django sont généralement capables de détecter et de demander si un modèle est renommé. Alors essayez d'abord manage.py makemigrations puis vérifiez le fichier de migration.

100
wasabigeek

Au début, je pensais que la méthode de Fiver fonctionnait pour moi, car la migration avait bien fonctionné jusqu'à l'étape 4. Cependant, les modifications implicites «ForeignKeyField (Foo)» dans «ForeignKeyField (Bar)» n'étaient associées à aucune migration. C'est pourquoi la migration a échoué lorsque j'ai voulu renommer les champs de relation (étapes 5 à 8) . Cela est peut-être dû au fait que mon 'AnotherModel' et 'YetAnotherModel' sont distribués dans d'autres applications dans mon cas.

J'ai donc réussi à renommer mes modèles et champs de relation en procédant comme suit:

J'ai adapté la méthode de this et particulièrement le tour d'otranzer.

Donc, comme Fiver, disons que nous avons dans myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Et dans myotherapp:

class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Étape 1:

Transformez chaque OntToOneField (Foo) ou ForeignKeyField (Foo) en IntegerField (). (Ceci gardera l'id de l'objet Foo associé comme valeur du champ entier).

class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.IntegerField()
    is_ridonkulous = models.BooleanField()

Ensuite 

python manage.py makemigrations

python manage.py migrate

Étape 2: (comme les étapes 2 à 4 de Fiver)

Changer le nom du modèle

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Créez une migration vide:

python manage.py makemigrations --empty myapp

Puis éditez le comme:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Finalement

python manage.py migrate

Étape 3:

Transformez votre IntegerField () en arrière dans son ancien ForeignKeyField, OneToOneField mais avec le nouveau modèle de barre. (Le champ entier précédent stockait l'identifiant, donc Django l'a compris et rétablit la connexion, ce qui est cool.)

class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_ridonkulous = models.BooleanField()

Alors fais:

python manage.py makemigrations 

Très important, à cette étape, vous devez modifier chaque nouvelle migration et ajouter la dépendance aux migrations RenameModel Foo-> Bar . Ainsi, si AnotherModel et YetAnotherModel sont tous deux dans myotherapp, la migration créée dans myotherapp doit ressembler à ceci:

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

Ensuite 

python manage.py migrate

Étape 4:

Finalement, vous pouvez renommer vos champs

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

et ensuite renommer automatiquement

python manage.py makemigrations

(Django devrait vous demander si vous avez renommé le nom du modèle, dites oui)

python manage.py migrate

Et c'est tout!

Cela fonctionne sur Django1.8

27
v.thorey

Je devais faire la même chose. J'ai changé le modèle en une seule fois (c'est-à-dire les étapes 1 et 5 ensemble). Puis créé une migration de schéma mais modifié comme suit:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('Foo','Bar')

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

Cela a parfaitement fonctionné. Toutes mes données existantes sont apparues, toutes les autres tables référencées sont correctes.

à partir d'ici: https://hanmir.wordpress.com/2012/08/30/rename-model-Django-south-migration/

7
John Q

Pour Django 1.10, j'ai réussi à modifier deux noms de classe de modèle (y compris une clé étrangère et des données) en exécutant simplement Makemigrations, puis Migrate pour l'application. Pour l'étape Makemigrations, je devais confirmer que je voulais changer les noms de table. Migrer a changé les noms des tables sans problème. 

Ensuite, j'ai modifié le nom du champ ForeignKey afin qu'il corresponde, et Makemigrations a demandé à nouveau de confirmer que je souhaitais changer le nom. Migrer que fait le changement.

J'ai donc pris cela en deux étapes sans aucune édition de fichier spéciale. J'ai eu des erreurs au début parce que j'avais oublié de changer le fichier admin.py, comme mentionné par @wasibigeek.

6
excyberlabber

J'ai également rencontré le problème décrit par v.thorey et constaté que son approche était très utile mais qu'elle pouvait être condensée en moins d'étapes. ci-dessous étape 3. Les étapes globales sont les suivantes:

Étape 1: Modifiez les noms de champs associés dans 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()

Étape 2: Créer une migration vide

python manage.py makemigrations --empty myapp

Étape 3: modifiez la classe de migration dans le fichier de migration créé à l'étape 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')
]

Étape 4: appliquez la migration

python manage.py migrate

Terminé

P.S. J'ai essayé cette approche sur Django 1.9

4
Curtis Lo

J'utilise Django version 1.9.4

J'ai suivi les étapes suivantes: -

Je viens de renommer le modèle oldName en NewName Exécutez python manage.py makemigrations. Il vous demandera Did you rename the appname.oldName model to NewName? [y/N] sélectionnez Y

Exécutez python manage.py migrate et il vous demandera de 

Les types de contenu suivants sont obsolètes et doivent être supprimés:

appname | oldName
appname | NewName

Tous les objets liés à ces types de contenu par une clé étrangère seront également supprimés. Etes-vous sûr de vouloir supprimer ces types de contenu?.

Type 'yes' to continue, or 'no' to cancel: Select No

Il renommer et migrer toutes les données existantes à la nouvelle table nommée pour moi.

4
Piyush S. Wanare

Malheureusement, j'ai rencontré des problèmes (chaque Django 1.x) avec la migration de renommage qui laissait les anciens noms de table dans la base de données (n'essayez même pas sur l'ancienne table, renommez juste modèle).

Solution pour ce cas:

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo
2

Je devais renommer quelques tables. Mais Django n'a remarqué qu'un seul changement de modèle. Cela est dû au fait que Django itère sur les modèles ajoutés, puis supprimés. Pour chaque paire, il vérifie si elles sont de la même application et ont champs identiques . Une seule table ne contenait aucune clé étrangère à des tables à renommer (les clés étrangères contiennent le nom de la classe de modèle, comme vous vous en souvenez). En d'autres termes, une seule table n'avait aucun changement de champ. C'est pourquoi cela a été remarqué.

La solution consiste donc à renommer une table à la fois, en modifiant le nom de la classe de modèle dans models.py, éventuellement views.py, et en effectuant une migration. Après cela, inspectez votre code pour d’autres références (noms de classe de modèle, noms associés (de requête), noms de variables). Effectuer une migration, si nécessaire. Ensuite, combinez éventuellement toutes ces migrations en une seule (assurez-vous également de copier les importations).

1
x-yuri

Je voulais juste confirmer et ajouter un commentaire ceasaro. Django 2.0 semble le faire automatiquement maintenant.

Je suis sur Jango 2.2.1, tout ce que je devais faire pour renommer le modèle et exécuter makemigrations.

Ici, il me demande si j'ai renommé la classe spécifique de A à B, j'ai choisi oui, j'ai migré et tout semble fonctionner.

Remarque Je n'ai renommé l'ancien nom de modèle dans aucun fichier du dossier project/migrations.

1
Peheje

Je voudrais faire @ceasaro mots, le mien sur son commentaire sur ce répondre

Les versions les plus récentes de Django peuvent détecter les modifications et demander ce qui a été fait ... Je voudrais également ajouter que Django peut mélanger l'ordre d'exécution de certaines commandes de migration. 

Il serait sage d'appliquer de petites modifications et d'exécuter makemigrations et migrate. Si l'erreur se produit, le fichier de migration peut être modifié. 

L'ordre d'exécution de certaines lignes peut être modifié pour éviter les erreurs.

1
diogosimao

Si vous utilisez un bon IDE comme PyCharm, vous pouvez cliquer avec le bouton droit sur le nom du modèle et faire un refactor -> renommer. Cela vous évite d'avoir à parcourir tout votre code faisant référence au modèle. Ensuite, lancez makemigrations et migrez. Django 2+ confirmera simplement le changement de nom.

0
Josh