Estrategia de migración de Django para renombrar un modelo y campos de relación

152

Estoy planeando cambiar el nombre de varios modelos en un proyecto Django existente donde hay muchos otros modelos que tienen relaciones de clave externa con los modelos que me gustaría cambiar el nombre. Estoy bastante seguro de que esto requerirá múltiples migraciones, pero no estoy seguro del procedimiento exacto.

Digamos que empiezo con los siguientes modelos dentro de una aplicación Django llamada 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()

Quiero cambiar el nombre del Foomodelo porque el nombre realmente no tiene sentido y está causando confusión en el código, y Barsería un nombre mucho más claro.

Por lo que he leído en la documentación de desarrollo de Django, estoy asumiendo la siguiente estrategia de migración:

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

Tenga en cuenta que el AnotherModelnombre del campo foono cambia, pero la relación se actualiza con el Barmodelo. Mi razonamiento es que no debería cambiar demasiado de una vez y que si cambiara este nombre de campo barcorrería el riesgo de perder los datos en esa columna.

Paso 2

Crea una migración vacía:

python manage.py makemigrations --empty myapp

Paso 3

Edite la Migrationclase en el archivo de migración creado en el paso 2 para agregar la RenameModeloperación a la lista de operaciones:

class Migration(migrations.Migration):

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

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

Paso 4

Aplicar la migración:

python manage.py migrate

Paso 5

Edite los nombres de campo relacionados en 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()

Paso 6

Crea otra migración vacía:

python manage.py makemigrations --empty myapp

Paso 7

Edite la Migrationclase en el archivo de migración creado en el paso 6 para agregar las RenameFieldoperaciones para cualquier nombre de campo relacionado a la lista de operaciones:

class Migration(migrations.Migration):

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

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

Paso 8

Aplicar la 2da migración:

python manage.py migrate

Además de actualizar el resto del código (vistas, formularios, etc.) para reflejar los nuevos nombres de variables, ¿es así básicamente cómo funcionaría la nueva funcionalidad de migración?

Además, esto parece muchos pasos. ¿Se pueden condensar las operaciones de migración de alguna manera?

¡Gracias!

Billete de cinco libras
fuente

Respuestas:

125

Entonces, cuando intenté esto, parece que puedes condensar los pasos 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')
    ]

Puede obtener algunos errores si no actualiza los nombres donde se importa, por ejemplo, admin.py e incluso archivos de migración más antiguos (!).

Actualización : como menciona Ceasaro , las versiones más nuevas de Django generalmente pueden detectar y preguntar si se cambia el nombre de un modelo. Intente manage.py makemigrationsprimero y luego verifique el archivo de migración.

wasabigeek
fuente
Gracias por la respuesta. Desde entonces, migré utilizando los pasos que describí, pero tengo curiosidad por saber si lo intentó con los datos existentes o simplemente con una base de datos vacía.
Fiver
2
Probado con los datos existentes, aunque sólo unas pocas filas en sqlite en mi env local (cuando me traslado a Producción tengo la intención de eliminar todo lo que fuera incluido migración de archivos.)
wasabigeek
44
No tiene que cambiar el nombre del modelo en los archivos de migración si los usa apps.get_model. Me tomó mucho tiempo resolver eso.
Ahmed
9
En django 2.0 si cambia el nombre de su modelo, el ./manage.py makemigrations myappcomando le preguntará si cambió el nombre de su modelo. Por ejemplo: ¿Cambiaste el nombre del modelo myapp.Foo a Bar? [y / N] Si responde 'y', su migración contendrá los migration.RenameModel('Foo', 'Bar')mismos recuentos para los campos renombrados :-)
ceasaro
1
manage.py makemigrations myappaún puede fallar: "Puede que tenga que agregar esto manualmente si cambia el nombre del modelo y algunos de sus campos a la vez; en el autodetector, parecerá que eliminó un modelo con el nombre anterior y agregó uno nuevo con un nombre diferente y la migración que crea perderá los datos de la tabla anterior ". Django 2.1 Docs Para mí, fue suficiente crear una migración vacía, agregarle el nombre del modelo y luego ejecutarla makemigrationscomo de costumbre.
hlongmore
36

Al principio, pensé que el método de Fiver funcionó para mí porque la migración funcionó bien hasta el paso 4. Sin embargo, los cambios implícitos 'ForeignKeyField (Foo)' en 'ForeignKeyField (Bar)' no estaban relacionados en ninguna migración. Es por eso que la migración falló cuando quería cambiar el nombre de los campos de relación (paso 5-8). Esto podría deberse al hecho de que mi 'AnotherModel' y 'YetAnotherModel' se envían en otras aplicaciones en mi caso.

Así que logré cambiar el nombre de mis modelos y campos de relación siguiendo los siguientes pasos:

Adapte el método de esto y particularmente el truco de otranzer.

Entonces, como Fiver, digamos que tenemos en myapp :

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

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

Paso 1:

Transforme cada OneToOneField (Foo) o ForeignKeyField (Foo) en IntegerField (). (Esto mantendrá la identificación del objeto Foo relacionado como valor del campo entero).

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

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

Luego

python manage.py makemigrations

python manage.py migrate

Paso 2: (como el paso 2-4 de Fiver)

Cambiar el nombre del modelo

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

Crea una migración vacía:

python manage.py makemigrations --empty myapp

Luego edítalo como:

class Migration(migrations.Migration):

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

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

Finalmente

python manage.py migrate

Paso 3:

Transforme su IntegerField () en su ForeignKeyField anterior o OneToOneField pero con el nuevo Modelo de barra. (El campo entero anterior estaba almacenando la identificación, por lo que django lo comprende y restablece la conexión, lo cual es genial).

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

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

Entonces hazlo:

python manage.py makemigrations 

Muy importante, en este paso debe modificar cada nueva migración y agregar la dependencia de las migraciones de RenameModel Foo-> Bar. Entonces, si AnotherModel y YetAnotherModel están en myotherapp, la migración creada en myotherapp debe verse así:

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

Luego

python manage.py migrate

Paso 4:

Eventualmente puedes renombrar tus 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()

y luego renombrar automáticamente

python manage.py makemigrations

(Django debería preguntarle si realmente cambió el nombre del modelo, diga sí)

python manage.py migrate

¡Y eso es!

Esto funciona en Django1.8

v.thorey
fuente
3
¡Gracias! Eso fue extremadamente útil. Pero una nota: también tuve que cambiar el nombre y / o eliminar los índices de campo de PostgreSQL a mano porque, después de cambiar el nombre de Foo a Bar, creé un nuevo modelo llamado Bar.
Anatoly Scherbakov
¡Gracias por esto! Creo que la parte clave es convertir todas las claves foráneas, dentro o fuera del modelo a renombrar, a IntegerField. Esto funcionó perfectamente para mí y tiene la ventaja adicional de que se recrean con el nombre correcto. ¡Naturalmente, recomendaría revisar todas las migraciones antes de ejecutarlas!
zelanix
¡Gracias! Intenté muchas estrategias diferentes para cambiar el nombre de un modelo para el que otros modelos tienen claves foráneas (pasos 1-3), y esta fue la única que funcionó.
MSH
Alterar ForeignKeys to IntegerFields me salvó el día hoy!
Mehmet
8

Necesitaba hacer lo mismo y seguir. Cambié el modelo de una vez (pasos 1 y 5 juntos de la respuesta de Fiver). Luego creó una migración de esquema, pero la editó para que sea así:

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

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

Esto funcionó perfectamente. Todos mis datos existentes aparecieron, todas las otras tablas hicieron referencia a Bar bien.

desde aquí: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

John Q
fuente
Genial, gracias por compartir. Asegúrese de +1 wasibigeek si esa respuesta ayudó.
Fiver
7

Para Django 1.10, logré cambiar dos nombres de clase de modelo (incluida una ForeignKey y con datos) simplemente ejecutando Makemigrations y luego Migrate para la aplicación. Para el paso Makemigrations, tuve que confirmar que quería cambiar los nombres de las tablas. Migrar cambió los nombres de las tablas sin ningún problema.

Luego cambié el nombre del campo ForeignKey para que coincida, y Makemigrations me pidió nuevamente que confirmara que quería cambiar el nombre. Migrar que hizo el cambio.

Así que tomé esto en dos pasos sin ninguna edición especial de archivos. Al principio recibí errores porque olvidé cambiar el archivo admin.py, como lo mencionó @wasibigeek.

excyberlabber
fuente
¡Muchas gracias! Perfecto para Django 1.11 también
Francisco
5

También enfrenté el problema como v.thorey describió y descubrí que su enfoque es muy útil, pero se puede condensar en menos pasos, que en realidad son los pasos 5 a 8 como Fiver describió sin los pasos 1 a 4, excepto que el paso 7 debe cambiarse como mi debajo del paso 3. Los pasos generales son los siguientes:

Paso 1: edite los nombres de campo relacionados en 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()

Paso 2: crea una migración vacía

python manage.py makemigrations --empty myapp

Paso 3: edite la clase de migración en el archivo de migración creado en el paso 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')
]

Paso 4: aplique la migración

python manage.py migrate

Hecho

PD: he probado este enfoque en Django 1.9

Curtis Lo
fuente
5

Estoy usando Django versión 1.9.4

He seguido los siguientes pasos: -

Acabo de cambiar el nombre del modelo oldName a NewName Run python manage.py makemigrations. Le pedirá que Did you rename the appname.oldName model to NewName? [y/N]seleccione Y

Corre python manage.py migratey te pedirá

Los siguientes tipos de contenido son obsoletos y deben eliminarse:

appname | oldName
appname | NewName

También se eliminarán los objetos relacionados con estos tipos de contenido mediante una clave externa. ¿Estás seguro de que deseas eliminar estos tipos de contenido? Si no está seguro, responda 'no'.

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

Cambia el nombre y migra todos los datos existentes a una nueva tabla con nombre para mí.

Piyush S. Wanare
fuente
Gracias amigo, estaba confundido porque no pasó nada cuando
golpeé
3

Desafortunadamente, encontré problemas (cada django 1.x) con la migración de cambio de nombre que dejan los viejos nombres de tablas en la base de datos.

Django ni siquiera intenta nada en la vieja mesa, solo cambia el nombre de su propio modelo. El mismo problema con las claves foráneas y los índices en general: Django no realiza un seguimiento adecuado de los cambios.

La solución más simple (solución alternativa):

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo  # and use Bar only

La solución real (una manera fácil de cambiar todos los índices, restricciones, desencadenantes, nombres, etc. en 2 confirmaciones, sino más bien para tablas más pequeñas ):

cometer A:

  1. crear el mismo modelo que el anterior
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. cambie el código para trabajar solo con el modelo nuevo Bar. (incluidas todas las relaciones en el esquema)

En la preparación de migración RunPython, que copia datos de Foo a Bar (incluso idde Foo)

  1. optimización opcional (si es necesario para tablas más grandes)

cometer B: (sin prisa, hágalo cuando se migra un equipo completo)

  1. caída segura del viejo modelo Foo

limpieza adicional:

  • aplastar las migraciones

error en Django:

Sławomir Lenart
fuente
3

Solo quería confirmar y agregar el comentario de Césaro. Django 2.0 parece hacer esto automáticamente ahora.

Estoy en Django 2.2.1, todo lo que tenía que hacer era cambiar el nombre del modelo y ejecutarlo makemigrations.

Aquí me pregunta si había cambiado el nombre de la clase específica de Aa B, elegí sí y ejecuté migrate y todo parece funcionar.

Tenga en cuenta que no cambié el nombre del modelo anterior en ningún archivo dentro de la carpeta del proyecto / migraciones.

Peheje
fuente
1

Necesitaba cambiar el nombre de un par de tablas. Pero Django solo notó un cambio de nombre de modelo. Eso sucedió porque Django itera sobre los modelos agregados y luego eliminados. Para cada par, verifica si son de la misma aplicación y tienen campos idénticos . Solo una tabla no tenía claves foráneas para cambiar el nombre de las tablas (las claves foráneas contienen el nombre de clase de modelo, como recordará). En otras palabras, solo una tabla no tenía cambios de campo. Por eso se notó.

Por lo tanto, la solución es cambiar el nombre de una tabla a la vez, cambiar el nombre de la clase de modelo models.py, posiblemente views.py, y realizar una migración. Después de eso, inspeccione su código para otras referencias (nombres de clase de modelo, nombres relacionados (consulta), nombres de variables). Realice una migración, si es necesario. Luego, opcionalmente combine todas estas migraciones en una (asegúrese de copiar también las importaciones).

x-yuri
fuente
1

Haría palabras de @ceasaro, mías en su comentario sobre esta respuesta .

Las versiones más recientes de Django pueden detectar cambios y preguntar qué se hizo. También agregaría que Django podría mezclar el orden de ejecución de algunos comandos de migración.

Sería aconsejable aplicar los cambios pequeños y ejecutar makemigrationsy migratey si el error se produce el archivo de migración puede ser editado.

Se puede cambiar el orden de ejecución de algunas líneas para evitar errores.

diogosimao
fuente
Bueno tener en cuenta que esto no funciona si cambia los nombres de modelo y hay claves externas definidas, etc ...
Dean Kayton
Ampliando el comentario anterior: si todo lo que hago es cambiar los nombres de los modelos y ejecutar makemigrations, obtengo 'NameError: name' <oldmodel> 'no está definido' en llaves extranjeras, etc. Si cambio eso y ejecuto makemigrations, obtengo errores de importación en admin.py ... si soluciono eso y ejecuto makemigrations nuevamente, recibo mensajes '¿Cambiaste el nombre del modelo <app.oldmodel> a <newmodel>' Pero luego, al aplicar las migraciones, obtengo 'ValueError: The field <app .newmodel.field1> se declaró con una referencia perezosa a '<app.oldmodel>', pero la aplicación '<app>' no proporciona el modelo '<oldmodel>', etc ... '
Dean Kayton
Parece que este error necesita cambiar el nombre de las referencias en sus migraciones históricas.
mhatch
@DeanKayton diría que migrations.SeparateDatabaseAndStatepuede ayudar?
diogosimao
1

Si está utilizando un buen IDE como PyCharm, puede hacer clic derecho en el nombre del modelo y hacer un refactorizador -> cambiar el nombre. Esto le ahorra la molestia de revisar todo el código que hace referencia al modelo. Luego ejecuta makemigrations y migra. Django 2+ simplemente confirmará el cambio de nombre.

Josh
fuente
-10

Actualicé Django de la versión 10 a la versión 11:

sudo pip install -U Django

( -Upara "actualización") y resolvió el problema.

Muhammad Hafid
fuente