web-dev-qa-db-fra.com

migration du changement de nom de champ du modèle Django sans perte de données

J'ai un projet Django avec une table de base de données qui contient déjà des données. J'aimerais changer le nom du champ sans perdre aucune des données de cette colonne. Mon plan initial était simplement de changer le nom du champ du modèle d'une manière qui ne modifierait pas réellement le nom de la table db (en utilisant le db_column paramètre de colonne):

Le modèle d'origine:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

Le nouveau modèle:

class Foo(models.Model):
  name = models.CharField(max_length=50, db_column='orig_name')

Mais, en exécutant schemamigration --auto produit un script de migration qui supprime la colonne d'origine, orig_name, et ajoute une nouvelle colonne, name, qui aurait pour effet secondaire indésirable de supprimer les données de cette colonne. (Je suis également confus quant à la raison pour laquelle South souhaite changer le nom de la colonne dans la base de données, car ma compréhension de db_column était qu'il permet une modification du nom du champ de modèle sans changer le nom de la colonne de la table de base de données).

Si je n'arrive pas à changer le champ modèle sans changer le champ db, je suppose que je pourrais faire un changement de nom plus simple comme ceci:

Le modèle d'origine:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

Le nouveau modèle:

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

Quelle que soit la stratégie que j'utilise (je préférerais la première, mais je trouverais la seconde acceptable), ma principale préoccupation est de ne pas perdre les données qui se trouvent déjà dans cette colonne.

Cela nécessite-t-il un processus en plusieurs étapes? (comme 1. ajouter une colonne, 2. migrer les données de l'ancienne colonne vers la nouvelle colonne et 3. supprimer la colonne d'origine) Ou puis-je modifier le script de migration avec quelque chose comme db.alter_column?

Quelle est la meilleure façon de conserver les données de cette colonne tout en changeant le nom de la colonne?

43
sfletche

Modification du nom de champ tout en conservant le champ DB

Ajout d'une réponse pour Django 1.8+ (avec les migrations natives de Django, plutôt que vers le sud).

Effectuez une migration qui ajoute d'abord un db_column, puis renomme le champ. Django comprend que le premier est un no-op (car il change le db_column pour rester le même), et que le second est un no-op (car il ne modifie pas le schéma). J'ai en fait examiné le journal pour voir qu'il n'y avait aucun changement de schéma ...

operations = [
    migrations.AlterField(
        model_name='mymodel',
        name='oldname',
        field=models.BooleanField(default=False, db_column=b'oldname'),
    ),
    migrations.RenameField(
        model_name='mymodel',
        old_name='oldname',
        new_name='newname',
    ),
]
40
Amichai Schreiber

C'est assez facile à réparer. Mais vous devrez modifier la migration vous-même.

Au lieu de supprimer et d'ajouter la colonne, utilisez db.rename_column . Vous pouvez simplement modifier la migration créée par schemamigration --auto

17
Wolph

MISE À JOUR 2

Même chose pour Django 2.2

MISE À JOUR 1

testé avec Django 2.0.9 , il peut détecter automatiquement si un champ a été renommé et donne une option pour renommer au lieu de supprimer et en créer un nouveau - enter image description here

Réponse initiale

Publier, s'il est toujours utile pour quelqu'un.

Pour Django 2.0 + renommez simplement le champ dans le modèle

class Foo(models.Model):
    orig_name = models.CharField(max_length=50)

à

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

Exécutez maintenant python manage.py makemigrations Cela va générer une migration avec des opérations pour supprimer l'ancien champ et ajouter le nouveau.

Allez-y et changez cela en suivant.

operations = [
    migrations.RenameField(
        model_name='foo',
        old_name='orig_name',
        new_name='name')
]

Exécutez maintenant python manage.py migrate il renommera la colonne dans la base de données sans perdre de données.

11
Aarif

En fait avec Django 1.10, en renommant simplement le champ dans le modèle puis en exécutant makemigrations, identifie immédiatement l'opération (c'est-à-dire qu'un champ a disparu, un autre est apparu à sa place):

$ ./manage.py makemigrations
Did you rename articlerequest.update_at to articlerequest.updated_at (a DateTimeField)? [y/N] y
Migrations for 'article_requests':
  article_requests/migrations/0003_auto_20160906_1623.py:
    - Rename field update_at on articlerequest to updated_at
7

J'ai rencontré cette situation. Je voulais changer les noms de champs dans le modèle mais garder les noms de colonnes identiques.

La façon dont je l'ai fait est de faire schemamigration --empty [app] [some good name for the migration]. Le problème est qu'en ce qui concerne South, la modification des noms de champs dans le modèle est un changement qu'il doit gérer. Il faut donc créer une migration. Cependant, nous savons qu'il n'y a rien à faire du côté de la base de données. Ainsi, une migration vide évite de faire des opérations inutiles sur la base de données et satisfait pourtant le besoin de South de gérer ce qu'il considère comme un changement.

Notez que si vous utilisez loaddata ou utilisez l'installation de test de Django (qui utilise loaddata en arrière-plan). Vous devrez mettre à jour les appareils pour utiliser le nouveau nom de champ, car les appareils sont basés sur les noms de champ de modèle, et non sur les noms de champ de base de données.

Pour les cas où les noms de colonne changent dans la base de données, je ne recommande jamais l'utilisation db.rename_column pour les migrations de colonnes. J'utilise la méthode décrite par sjh dans cette réponse :

J'ai ajouté la nouvelle colonne en une seule migration de schéma, puis créé une migration de données pour déplacer les valeurs dans le nouveau champ, puis une deuxième migration de schéma pour supprimer l'ancienne colonne

Comme je l'ai noté dans un commentaire sur cette question, le problème avec db.rename_column est qu'il ne renomme pas les contraintes avec la colonne. Je ne sais pas si le problème est purement cosmétique ou si cela signifie qu'une future migration peut échouer car elle ne peut pas trouver de contrainte.

5
Louis

Il est possible de renommer un champ sans effectuer de modification manuelle du fichier de migration:

  1. Ajoutez db_column = OLD_FIELD_NAME au champ d'origine.
  2. Courir: python3 manage.py makemigrations
  3. Renommez le champ de OLD_FIELD_NAME à NEW_FIELD_NAME
  4. Courir: python3 manage.py makemigrations

Vous serez invité:

Avez-vous renommé MODÈLE .OLD_FIELD_NAME en MODÈLE . NEW_FIELD_NAME (une ForeignKey)? [y/N] y

Cela générera deux fichiers de migration plutôt qu'un seul, bien que les deux migrations soient générées automatiquement.

Cette procédure fonctionne sur Django 1.7+.

1
David Foster

Je suis tombé sur cette situation sur Django 1.7.7. J'ai fini par faire ce qui suit qui a fonctionné pour moi.

./manage.py makemigrations <app_name> --empty

Ajout d'une sous-classe simple de migrations.RenameField qui ne touche pas la base de données:

class RenameFieldKeepDatabaseColumn(migrations.RenameField):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
    pass

def database_forwards(self, app_label, schema_editor, from_state, to_state):
    pass
1
alphanumeric0