Las migraciones son la forma que tiene Django de propagar los cambios que hagas en tus modelos (agregar un campo, eliminar un modelo, etc.) a tu esquema de base de datos. Están diseñadas para ser casi automáticas, pero necesitarás saber cuándo hacer migraciones, cuando ejecutarlas y los problemas comunes con los que podrías tropezar.
Hay varios comandos que utilizarás para interactuar con las migraciones y la forma en que Django maneja el esquema de base de datos:
migrate, responsable de aplicar y desaplicar las migraciones.
makemigrations, responsable de crear nuevas migraciones basadas en los cambios que hayas realizado en tus modelos.
sqlmigrate, que muestra las sentencias SQL para una migración.
showmigrations, que lista las migraciones de un proyecto y su estado.
Deberías considerar las migraciones como un sistema de control de versiones para el esquema de base de datos. makemigrations se encarga de empaquetar los cambios en tus modelos en archivos de migración individuales - análogos a los commits - y migrate se encarga de aplicarlos a tu base de datos.
Los archivos de migración para cada aplicación viven en un directorio «migrations» dentro de esa aplicación, y están diseñados para ser comprometidos y distribuidos como parte del códigobase. Deberías crearlos una vez en tu máquina de desarrollo y luego ejecutar las mismas migraciones en las máquinas de tus colegas, las máquinas de staging y finalmente las máquinas de producción.
Nota
Es posible sobrescribir el nombre del paquete que contiene las migraciones en una base por aplicación modificando la MIGRATION_MODULES configuración.
Las migraciones se ejecutarán de la misma manera en el mismo conjunto de datos y producirán resultados consistentes, lo que significa que lo que ves en desarrollo y staging es exactamente lo que sucederá en producción bajo las mismas circunstancias.
Django creará migraciones para cualquier cambio en tus modelos o campos - incluso opciones que no afectan la base de datos - ya que la única forma en que puede reconstruir un campo correctamente es tener todos los cambios en la historia, y podrías necesitar esas opciones en algunas migraciones de datos más adelante (por ejemplo, si has establecido validadores personalizados).
Las migraciones están soportadas en todos los backends que se envían con Django, así como en cualquier backend de terceros si han programado el soporte para la alteración del esquema (hecho a través de la clase SchemaEditor).
Sin embargo, algunas bases de datos son más capaces que otras cuando se trata de migraciones de esquemas; algunos de los inconvenientes están cubiertos a continuación.
PostgreSQL es el más capaz de todas las bases de datos aquí en términos de soporte para esquemas.
MySQL carece de soporte para transacciones alrededor de operaciones de alteración del esquema, lo que significa que si una migración falla al aplicarse tendrás que deshacer manualmente los cambios para intentarlo de nuevo (no es posible retroceder a un punto anterior).
MySQL 8.0 introdujo mejoras significativas en el rendimiento para operaciones DDL, lo que las hace más eficientes y reduce la necesidad de reconstruir tablas completas. Sin embargo, no puede garantizar una ausencia completa de bloqueos o interrupciones. En situaciones donde los bloqueos todavía son necesarios, la duración de estas operaciones será proporcional al número de filas involucradas.
Finalmente, MySQL tiene un límite relativamente pequeño en el tamaño combinado de todas las columnas que cubre un índice. Esto significa que los índices que son posibles en otros backends fallarán a ser creados bajo MySQL.
SQLite tiene muy poco soporte para la alteración del esquema incorporado, y por lo tanto Django intenta emularlo mediante:
Creando una nueva tabla con el nuevo esquema
Copiando los datos a través
Eliminando la antigua tabla
Renombrando la nueva tabla para que coincida con el nombre original
Este proceso funciona generalmente bien, pero puede ser lento y ocasionalmente buggy. No se recomienda ejecutar y migrar SQLite en un entorno de producción a menos que estés muy consciente de los riesgos y sus limitaciones; el soporte que Django incluye está diseñado para permitir a los desarrolladores utilizar SQLite en sus máquinas locales para desarrollar proyectos Django menos complejos sin la necesidad de una base de datos completa.
Django puede crear migraciones por ti. Haz cambios en tus modelos - digamos, agrega un campo y elimina un modelo - y luego ejecuta makemigrations:
$ python manage.py makemigrations
Migrations for 'books':
books/migrations/0003_auto.py:
~ Alter field author on book
Tus modelos se escanearán y se compararán con las versiones actuales contenidas en tus archivos de migración, y luego se escribirá una nueva serie de migraciones. Asegúrate de leer la salida para ver qué makemigrations piensa que has cambiado - no es perfecto, y para cambios complejos puede no detectar lo que esperas.
Una vez que tengas tus nuevos archivos de migración, debes aplicarlos a tu base de datos para asegurarte de que funcionen como se espera:
$ python manage.py migrate
Operations to perform:
Apply all migrations: books
Running migrations:
Rendering model states... DONE
Applying books.0003_auto... OK
Una vez que la migración esté aplicada, haz un commit de la migración y el cambio del modelo en tu sistema de control de versiones como un solo commit - de esa manera, cuando otros desarrolladores (o tus servidores de producción) revisiten el código, obtendrán tanto los cambios en tus modelos como la migración acompañante al mismo tiempo.
Si deseas darle un nombre significativo a la migración (migraciones) en lugar de uno generado automáticamente, puedes utilizar la opción makemigrations --name:
$ python manage.py makemigrations --name changed_my_model your_app_label
Porque las migraciones se almacenan en control de versiones, ocasionalmente te encontrarás con situaciones en las que tú y otro desarrollador habéis comprometido una migración a la misma aplicación en el mismo momento, lo que resulta en dos migraciones con el mismo número.
No te preocupes - los números están allí solo para referencia de los desarrolladores. Django solo se preocupa de que cada migración tenga un nombre diferente. Las migraciones especifican qué otras migraciones dependen de ellas, incluyendo las migraciones anteriores en la misma aplicación, en el archivo, por lo que es posible detectar cuando hay dos nuevas migraciones para la misma aplicación que no están ordenadas.
Cuando esto sucede, Django te preguntará y te dará algunas opciones. Si piensa que es seguro suficiente, ofrecerá automatizar la linearización de las dos migraciones por ti. Si no, tendrás que ir a modificar las migraciones tú mismo - no te preocupes, esto no es difícil, y se explica más en Archivos de migración a continuación.
En bases de datos que admiten transacciones DDL (SQLite y PostgreSQL), todas las operaciones de migración correrán dentro de una sola transacción por defecto. En contraste, si una base de datos no admite transacciones DDL (por ejemplo, MySQL, Oracle) entonces todas las operaciones correrán sin una transacción.
Puedes evitar que una migración se ejecute en una transacción estableciendo el atributo atomic a False. Por ejemplo:
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
También es posible ejecutar partes de la migración dentro de una transacción utilizando atomic() o pasando atomic=True a RunPython. Consulta non-atomic-migraciones para obtener más detalles.
Si bien las migraciones son por aplicación, las tablas y relaciones implicadas en tus modelos son demasiado complejas como para crearlas una a la vez. Cuando haces una migración que requiere otra cosa - por ejemplo, agregas un ForeignKey en tu aplicación books a tu aplicación authors - la migración resultante contendrá una dependencia de una migración en authors.
Este significa que cuando ejecutes las migraciones, la migración authors se ejecuta primero y crea la tabla a la que se refiere el ForeignKey, y luego la migración que crea la columna del ForeignKey se ejecuta después y crea la restricción. Si esto no sucediera, la migración intentaría crear la columna del ForeignKey sin que exista la tabla a la que se refiere y tu base de datos lanzaría un error.
Este comportamiento de dependencia afecta a la mayoría de las operaciones de migración donde restringes a una sola aplicación. Restringir a una sola aplicación (ya sea en makemigrations o migrate) es una promesa de mejores esfuerzos, y no una garantía; cualquier otra aplicación que necesite usarse para obtener las dependencias correctas será.
Las aplicaciones sin migraciones no deben tener relaciones (ForeignKey, ManyToManyField, etc.) con aplicaciones que tienen migraciones. A veces puede funcionar, pero no está soportado.
La función swappable_dependency() se utiliza en las migraciones para declarar «dependencias intercambiables» sobre migraciones en la aplicación del modelo que se sustituye, actualmente, en la primera migración de esta aplicación. Como consecuencia, el modelo sustituido debe crearse en la migración inicial. El argumento value es una cadena "<app label>.<model>" que describe un etiqueta de aplicación y un nombre de modelo, por ejemplo "myapp.MyModel".
Al utilizar swappable_dependency(), informas al marco de trabajo de migración que la migración depende de otra migración que configura un modelo intercambiable, lo que permite la posibilidad de sustituir el modelo con una implementación diferente en el futuro. Esto se utiliza típicamente para referenciar modelos que están sujetos a personalización o reemplazo, como el modelo de usuario personalizado (settings.AUTH_USER_MODEL, que por defecto es "auth.User") en el sistema de autenticación de Django.
Las migraciones se almacenan en un formato en disco, denominado aquí «archivos de migración». Estos archivos son realmente archivos Python normales con una disposición de objetos acordada, escrita en estilo declarativo.
Una migración básica se ve así:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("migrations", "0001_initial")]
operations = [
migrations.DeleteModel("Tribble"),
migrations.AddField("Author", "rating", models.IntegerField(default=0)),
]
Lo que busca Django cuando carga un archivo de migración (como un módulo de Python) es una subclase de django.db.migrations.Migration llamada Migration. Luego inspecciona este objeto por cuatro atributos, solo dos de los cuales se utilizan la mayoría del tiempo:
dependencies, una lista de migraciones en las que esta depende.
operations, una lista de clases Operation que definen qué hace esta migración.
Las operaciones son la clave; son un conjunto de instrucciones declarativas que le dicen a Django qué cambios en el esquema necesitan ser realizados. Django las escanea y construye una representación en memoria de todos los cambios en el esquema para todas las aplicaciones, y utiliza esto para generar el SQL que realiza los cambios en el esquema.
Esa estructura en memoria también se utiliza para trabajar out qué son las diferencias entre tus modelos y el estado actual de tus migraciones; Django pasa por todos los cambios, en orden, sobre un conjunto en memoria de modelos para llegar al estado de tus modelos la última vez que ejecutaste makemigrations. Luego utiliza estos modelos para compararlos con los que están en tus archivos models.py para trabajar out qué has cambiado.
Deberías raramente, si es que nunca, necesitar editar archivos de migración a mano, pero está completamente posible escribirlos manualmente si lo necesitas. Algunas de las operaciones más complejas no son autodetectables y solo están disponibles mediante una migración escrita a mano, así que no tengas miedo de editarlo si tienes que hacerlo.
No puedes modificar el número de argumentos posicionales en un campo personalizado ya migrado sin levantar un TypeError. La antigua migración llamará al método modificado __init__ con la firma antigua. Así que si necesitas un nuevo argumento, crea uno por palabra clave y agrega algo como assert 'argument_name' in kwargs en el constructor.
Puedes serializar opcionalmente administradores a migraciones y tenerlos disponibles en operaciones RunPython. Esto se hace definiendo un atributo use_in_migrations en la clase del administrador:
class MyManager(models.Manager):
use_in_migrations = True
class MyModel(models.Model):
objects = MyManager()
Si estás utilizando la función from_queryset() para generar dinámicamente una clase de administrador, necesitas heredar de la clase generada para hacerla importable:
class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
use_in_migrations = True
class MyModel(models.Model):
objects = MyManager()
Los textos traducidos manteniendo todas sus etiquetas intactas son:
Las «migraciones iniciales» de una aplicación son las migraciones que crean la primera versión de las tablas de esa aplicación. Normalmente, una aplicación tendrá una sola migración inicial, pero en algunos casos de dependencias complejas entre modelos puede tener dos o más.
Las migraciones iniciales se marcan con un atributo de clase initial = True. Si no se encuentra un atributo initial, una migración se considerará «inicial» si es la primera migración en la aplicación (es decir, si no depende de ninguna otra migración en la misma aplicación).
Cuando se utiliza la opción migrate --fake-initial, estas migraciones iniciales se tratan de manera especial. Para una migración inicial que crea uno o más tablas (operación CreateModel), Django verifica que todas esas tablas ya existen en la base de datos y aplica fake la migración si es así. De manera similar, para una migración inicial que agrega uno o más campos (operación AddField), Django verifica que todos los respectivos columnas ya existan en la base de datos y aplica fake la migración si es así. Sin --fake-initial, las migraciones iniciales se tratan igual que cualquier otra migración.
Como se discutió anteriormente, puede necesitar linearizar manualmente las migraciones cuando dos ramas de desarrollo se unen. Mientras edita las dependencias de las migraciones, puedes crear involuntariamente un estado inconsistente de la historia donde una migración ha sido aplicada pero algunas de sus dependencias no lo han hecho. Esto es una fuerte indicación de que las dependencias son incorrectas, por lo que Django se negará a ejecutar migraciones o hacer nuevas migraciones hasta que esté resuelto. Al utilizar múltiples bases de datos, puedes usar el método allow_migrate() del router de base de datos <topics-db-multi-db-routing> para controlar qué bases de datos makemigrations verifica para una consistente historia.
Las nuevas aplicaciones vienen preconfiguradas para aceptar migraciones, por lo que puedes agregarlas ejecutando makemigrations después de haber realizado algunos cambios.
Si tu aplicación ya tiene modelos y tablas de base de datos, pero no tiene migraciones (por ejemplo, si la creaste contra una versión anterior de Django), necesitarás convertirla para que utilice migraciones ejecutando:
$ python manage.py makemigrations your_app_label
Este será un nuevo fichero de migración inicial para tu aplicación. Ahora, ejecuta python manage.py migrate --fake-initial, y Django detectará que tienes una migración inicial y que las tablas que quiere crear ya existen, y marcará la migración como aplicada. (Sin la bandera migrate --fake-initial, el comando daría error porque las tablas que quiere crear ya existen.)
Ten en cuenta que esto solo funciona si se cumplen dos condiciones:
No has cambiado tus modelos desde que creaste sus tablas. Para que las migraciones funcionen, debes hacer la migración inicial primero y luego hacer cambios, ya que Django compara los cambios contra los ficheros de migración, no contra la base de datos.
No has editado manualmente tu base de datos - Django no podrá detectar que tu base de datos no coincide con tus modelos, solo obtendrás errores cuando las migraciones intenten modificar esas tablas.
Las migraciones se pueden revertir con migrate pasando el número de la migración anterior. Por ejemplo, para revertir la migración books.0003:
$ python manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto... OK
Si deseas revertir todas las migraciones aplicadas para una aplicación, utiliza el nombre zero:
$ python manage.py migrate books zero
Operations to perform:
Unapply all migrations: books
Running migrations:
Rendering model states... DONE
Unapplying books.0002_auto... OK
Unapplying books.0001_initial... OK
Una migración es irreversible si contiene operaciones irreversibles. Al intentar revertirla se levantará un IrreversibleError.
$ python manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
Cuando ejecutas las migraciones, Django está trabajando con versiones históricas de tus modelos almacenadas en los ficheros de migración. Si escribes código Python utilizando la operación RunPython, o si tienes métodos allow_migrate en tus routers de base de datos, debes utilizar estas versiones de modelos históricos en lugar de importarlos directamente.
Advertencia
Si importas modelos directamente en lugar de utilizar los modelos históricos, tus migraciones pueden funcionar inicialmente, pero fallarán en el futuro cuando intentes volver a ejecutar las migraciones antiguas (comúnmente, cuando configuras una nueva instalación y ejecutas todas las migraciones para configurar la base de datos).
Esto significa que los problemas con modelos históricos pueden no ser inmediatamente obvios. Si te encuentras con este tipo de falla, está bien editar la migración para utilizar los modelos históricos en lugar de importaciones directas y cometer esos cambios.
Porque es imposible serializar código Python arbitrario, estos modelos históricos no tendrán métodos personalizados que hayas definido. Sin embargo, tendrán los mismos campos, relaciones, administradores (limitados a aquellos con use_in_migrations = True) y opciones de Meta (también versionadas, por lo que pueden ser diferentes de las actuales).
Advertencia
Esto significa que NO se llamará a métodos personalizados save() en objetos cuando los accedas en migraciones, y NO tendrás ningún constructor o método de instancia personalizado. Planifica adecuadamente.
Las referencias a funciones en opciones de campos como upload_to y limit_choices_to y declaraciones del administrador de modelos con administradores que tienen use_in_migrations = True se serializan en migraciones, por lo que las funciones y clases deben mantenerse durante tanto tiempo como haya una migración que las refiera. Cualquier campo de modelo personalizado también debe mantenerse, ya que estas son importadas directamente por las migraciones.
Además, las clases base concretas del modelo se almacenan como punteros, por lo que debes mantener siempre las clases base mientras haya una migración que contenga una referencia a ellas. Por otro lado, los métodos y administradores de estas clases heredan normalmente, por lo que si necesitas acceder a estos puedes optar a moverlos a una superclase.
Para eliminar referencias antiguas, puedes unir migraciones o, si no hay muchas referencias, copiarlas en los archivos de las migraciones.
De manera similar a las «consideraciones sobre referencias a funciones históricas» descritas en la sección anterior, eliminar campos de modelo personalizados de tu proyecto o aplicación de terceros causará un problema si están referenciados en migraciones antiguas.
Para ayudar con esta situación, Django proporciona algunas atributos de campo de modelo para ayudar a la desactivación del uso de campos de modelo utilizando el sistema de comprobaciones.
A continuación se presentan las traducciones de los textos originales manteniendo todas sus etiquetas intactas.
class IPAddressField(Field):
system_check_deprecated_details = {
"msg": (
"IPAddressField has been deprecated. Support for it (except "
"in historical migrations) will be removed in Django 1.9."
),
"hint": "Use GenericIPAddressField instead.", # optional
"id": "fields.W900", # pick a unique ID for your field.
}
Después de un período de desuso que tú elijas (dos o tres versiones de características para campos en Django mismo), cambia la atributo system_check_deprecated_details a system_check_removed_details y actualiza el diccionario similar a:
class IPAddressField(Field):
system_check_removed_details = {
"msg": (
"IPAddressField has been removed except for support in "
"historical migrations."
),
"hint": "Use GenericIPAddressField instead.",
"id": "fields.E900", # pick a unique ID for your field.
}
Debes mantener los métodos del campo que son necesarios para que funcione en las migraciones de base de datos, como __init__(), deconstruct() y get_internal_type(). Mantén este campo vacío mientras existan cualquier migración que lo refiera. Por ejemplo, después de fusionar migraciones y eliminar las antiguas, puedes eliminar completamente el campo.
Además de cambiar la estructura de la base de datos, también puedes utilizar migraciones para cambiar los datos en la base de datos misma, junto con la estructura si lo deseas.
Las migraciones que alteran los datos suelen llamarse «migraciones de datos»; es mejor escribirlas como migraciones separadas, sentadas al lado de las migraciones de esquema.
Django no puede generar automáticamente migraciones de datos para ti, ya que lo hace con las migraciones de esquema, pero no es muy difícil escribirlas. Los archivos de migración en Django están compuestos por Operaciones, y la operación principal que utilizas para las migraciones de datos es RunPython.
Para empezar, crea un archivo de migración vacío desde el cual puedas trabajar (Django colocará el archivo en el lugar correcto, sugerirá un nombre y agregará dependencias para ti):
python manage.py makemigrations --empty yourappname
Luego, abre el archivo; debería parecerse a esto:
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("yourappname", "0001_initial"),
]
operations = []
Ahora solo necesitas crear una nueva función y que RunPython la utilice. RunPython espera un llamable como argumento que tome dos argumentos - el primero es un registro de aplicaciones que tiene las versiones históricas de todos tus modelos cargadas en él para coincidir con dónde en tu historia la migración se encuentra, y el segundo es un Editor de Esquema, que puedes utilizar para efectuar cambios manuales en la estructura de base de datos (pero ten cuidado, hacer esto puede confundir al autodetector de migraciones!).
Escribe una migración que puebla nuestro nuevo campo name con los valores combinados de first_name y last_name (hemos vuelto en nosotrxs y hemos llegado a la conclusión de que no todos tienen nombres de primeras y segundas letras). Todo lo que necesitamos hacer es utilizar el modelo histórico e iterar sobre las filas:
from django.db import migrations
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = f"{person.first_name} {person.last_name}"
person.save()
class Migration(migrations.Migration):
dependencies = [
("yourappname", "0001_initial"),
]
operations = [
migrations.RunPython(combine_names),
]
Una vez que se ha realizado esto, podemos ejecutar python manage.py migrate de manera normal y la migración de datos se ejecutará en su lugar junto con otras migraciones.
Puedes pasar un segundo llamable a RunPython para ejecutar la lógica que desees cuando se migra hacia atrás. Si este llamable está omitido, migrar hacia atrás levantará una excepción.
Cuando se escribe una función RunPython que utiliza modelos de aplicaciones distintas a la en la que está ubicada la migración, el atributo dependencies de la migración debe incluir la última migración de cada aplicación involucrada, de lo contrario puede obtener un error similar a: LookupError: No installed app with label 'myappname' cuando se intenta recuperar el modelo en la función RunPython utilizando apps.get_model().
En el ejemplo siguiente, tenemos una migración en app1 que necesita utilizar modelos en app2. No nos preocupamos por los detalles de move_m1 más allá del hecho de que necesitará acceder a modelos de ambas aplicaciones. Por lo tanto, hemos agregado una dependencia que especifica la última migración de app2:
class Migration(migrations.Migration):
dependencies = [
("app1", "0001_initial"),
# added dependency to enable using models from app2 in move_m1
("app2", "0004_foobar"),
]
operations = [
migrations.RunPython(move_m1),
]
Si estás interesado en las operaciones de migración más avanzadas o quieres poder escribir tus propias, consulta la referencia a las operaciones de migración <ref/migration-operations>` y el «cómo hacerlo» sobre <howto/writing-migrations>.
Se te anima a realizar migraciones de manera libre y sin preocuparte por cuántas tengas; el código de la migración está optimizado para manejar cientos al mismo tiempo sin un gran retraso. Sin embargo, eventualmente querrás volver a tener varias docenas de migraciones en lugar de pocas, y es ahí donde entra en juego la compactación.
La reducción de la cantidad de migraciones existentes a una sola (o algunas) que representan los mismos cambios es el acto de reducir un conjunto de muchas migraciones existentes.
Django realiza esto tomando todas las migraciones existentes, extrayendo sus Operations y colocándolos en secuencia, y luego ejecutando un optimizador sobre ellos para tratar de reducir la longitud de la lista - por ejemplo, sabe que CreateModel y DeleteModel se cancelan entre sí, y sabe que AddField puede ser incorporado a CreateModel.
Una vez que la secuencia de operaciones ha sido reducida lo más posible - la cantidad posible depende de cuán estrechamente interconectados sean tus modelos y si tienes alguna RunSQL o RunPython operación (que no pueden ser optimizadas a menos que se marquen como elidable) - Django escribirá de nuevo en un conjunto nuevo de archivos de migraciones.
Estos archivos están marcados para indicar que reemplazan las migraciones previamente reducidas, por lo que pueden coexistir con los antiguos archivos de migración, y Django cambiará entre ellos de manera inteligente dependiendo de dónde estés en la historia. Si todavía estás a medio camino del conjunto de migraciones que redujiste, seguirá utilizando las antiguas hasta llegar al final y luego cambiará a la historia reducida, mientras que los nuevos instancias utilizarán la nueva migración reducida y saltarán todas las antiguas.
Esto te permite reducir sin afectar sistemas en producción que no están completamente actualizados. El proceso recomendado es reducir, mantener los archivos antiguos, confirmar y liberar, esperar a que todos los sistemas estén actualizados con la nueva versión (o si eres un proyecto de terceros, asegúrate de que tus usuarios actualicen las versiones en orden sin saltarse ninguna), y luego eliminar los archivos antiguos, confirmar y hacer una segunda liberación.
La orden que respalda todo esto es squashmigrations - pasa el etiqueta de aplicación y nombre de migración que deseas reducir hasta allí, y se pondrá a trabajar:
$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
- 0001_initial
- 0002_some_change
- 0003_another_change
- 0004_undo_something
Do you wish to proceed? [y/N] y
Optimizing...
Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.py
You should commit this migration but leave the old ones in place;
the new migration will be used for new installs. Once you are sure
all instances of the codebase have applied the migrations you squashed,
you can delete them.
Utiliza la opción squashmigrations --squashed-name si deseas establecer el nombre de la migración reducida en lugar de utilizar uno autogenerado.
Ten en cuenta que las interdependencias entre modelos en Django pueden volverse muy complejas, y reducir puede resultar en migraciones que no se ejecutan; ya sea mal optimizadas (en cuyo caso puedes intentarlo de nuevo con --no-optimize, aunque también deberías informar un problema), o con un CircularDependencyError, en cuyo caso puedes resolverlo manualmente.
Para resolver manualmente un CircularDependencyError, divide a uno de los ForeignKeys en el bucle de dependencia circular en una migración separada, y mueve la dependencia en el otro paquete con ella. Si no estás seguro, mira cómo makemigrations maneja el problema cuando se le pide crear nuevas migraciones a partir de tus modelos. En una futura versión de Django, squashmigrations será actualizado para intentar resolver estos errores por sí mismo.
Una vez que hayas reducido tu migración, debes confirmarla junto con las migraciones que reemplaza y distribuir este cambio a todas las instancias en ejecución de tu aplicación, asegurándote de que corran migrate para almacenar el cambio en su base de datos.
Debido a que el texto original ya está en formato Sphinx y contiene marcas de código y referencias internas, mi tarea es simplemente traducir el contenido manteniendo intactos los elementos no traducibles.
Borrar todos los archivos de migración que reemplaza.
Actualizar todas las migraciones que dependen de las migraciones eliminadas para que dependan en su lugar de la migración aplastada.
Eliminar el atributo replaces en la clase Migration de la migración aplastada (esto es cómo Django indica que se trata de una migración aplastada).
Nota
Una vez que hayas aplastado una migración, no deberías re-aplastar esa migración aplastada hasta haberla transicionado completamente a una migración normal.
Eliminando referencias a las migraciones eliminadas
Si es probable que puedas reutilizar el nombre de una migración eliminada en el futuro, deberías eliminar referencias a ella de la tabla de migraciones de Django con la opción migrate --prune.
Las migraciones son archivos Python que contienen las definiciones antiguas de tus modelos - por lo tanto, para escribirlas, Django debe tomar el estado actual de tus modelos y serializarlos en un archivo.
Si bien Django puede serializar la mayoría de las cosas, hay algunas cosas que simplemente no podemos serializar en una representación válida de Python - no existe estándar de Python para cómo convertir un valor nuevamente a código (repr() solo funciona con valores básicos y no especifica rutas de importación).
Django puede serializar los siguientes tipos de datos:
int, float, bool, str, bytes, None, NoneType
list, set, tuple, dict, range.
Instancias de datetime.date, datetime.time y datetime.datetime (incluyendo aquellas que están en una zona horaria)
Instancias de decimal.Decimal
Instancias de enum.Enum y enum.Flag
Instancias de uuid.UUID
functools.partial() e instancias de functools.partialmethod que tienen valores serializables para func, args y keywords.
Objetos de ruta puros y concretos desde pathlib. Las rutas concretas se convierten en su equivalente de ruta pura, por ejemplo pathlib.PosixPath a pathlib.PurePosixPath.
Instancias de os.PathLike, por ejemplo os.DirEntry, que se convierten a str o bytes utilizando os.fspath().
LazyObject instancias que envuelven un valor serializable.
Tipos de enumeración (por ejemplo, TextChoices o IntegerChoices) instancias.
Cualquier campo Django
Cualquier referencia a función o método (por ejemplo, datetime.datetime.today) (debe estar en el ámbito superior del módulo)
Las funciones pueden decorarse si se envuelven correctamente, es decir, utilizando functools.wraps()
Los decoradores functools.cache() y functools.lru_cache() están explícitamente soportados
Métodos no vinculados utilizados desde el cuerpo de la clase
Cualquier referencia a clase (debe estar en el ámbito superior del módulo)
Cualquier cosa con un método deconstruct() personalizado (ver abajo)
Django no puede serializar:
Nested classes
Instancias de clases arbitrarias (por ejemplo, MyClass(4.3, 5.7))
Lambdas
Puedes serializar otros tipos escribiendo un serializador personalizado. Por ejemplo, si Django no serializara por defecto Decimal, podrías hacer esto:
from decimal import Decimal
from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter
class DecimalSerializer(BaseSerializer):
def serialize(self):
return repr(self.value), {"from decimal import Decimal"}
MigrationWriter.register_serializer(Decimal, DecimalSerializer)
El primer argumento de MigrationWriter.register_serializer() es un tipo o iterable de tipos que deben utilizar el serializador.
El método serialize() de tu serializador debe devolver una cadena de cómo debería aparecer el valor en las migraciones y un conjunto de cualquier importación necesaria en la migración.
deconstruct()¶Puedes dejar que Django serialize tus propias instancias de clases personalizadas dando a la clase un método deconstruct(). No recibe argumentos, y debe devolver una tupla de tres cosas (path, args, kwargs):
path debería ser el camino Python a la clase, con el nombre de la clase incluido como parte final (por ejemplo, myapp.custom_things.MyClass). Si tu clase no está disponible en el nivel superior de un módulo no es serializable.
Los textos traducidos son:
kwargs debería ser un diccionario de argumentos clave-valor para pasar a la método __init__ de tu clase. Cada valor debería poder serializarse.
Nota
Este valor de retorno es diferente del método deconstruct() para campos personalizados que devuelve una tupla de cuatro elementos.
Django escribirá el valor como una instantiación de tu clase con los argumentos dados, similar a la forma en que escribe referencias a campos Django.
Para evitar crear una nueva migración cada vez que se ejecuta makemigrations, también deberías agregar un método __eq__() a la clase decorada. Este método será llamado por el marco de trabajo de migraciones de Django para detectar cambios entre estados.
Mientras todos los argumentos del constructor de tu clase sean ellos mismos serializables, puedes usar el decorador de clase @deconstructible desde django.utils.deconstruct para agregar el método deconstruct():
from django.utils.deconstruct import deconstructible
@deconstructible
class MyCustomClass:
def __init__(self, foo=1):
self.foo = foo
...
def __eq__(self, other):
return self.foo == other.foo
El decorador agrega lógica para capturar y preservar los argumentos en su camino hacia tu constructor, y luego devuelve esos argumentos exactamente cuando se llama a deconstruct().
Si eres el mantenedor de una aplicación tercera con modelos, puede que necesites enviar migraciones que soporten múltiples versiones de Django. En este caso, siempre deberías ejecutar makemigrations con la versión más baja de Django que desees soportar.
El sistema de migraciones mantendrá la compatibilidad hacia atrás según la misma política del resto de Django, por lo que los archivos de migración generados en Django X.Y deberían poder ejecutarse sin cambios en Django X.Y+1. El sistema de migraciones no promete compatibilidad hacia adelante, sin embargo. Se pueden agregar nuevas características y los archivos de migración generados con versiones más nuevas de Django pueden no funcionar en versiones anteriores.
Ver también
Cubre las operaciones del API de esquema, operaciones especiales y la escritura de tus propias operaciones.
Explica cómo estructurar y escribir migraciones de base de datos para diferentes escenarios que podrías encontrar.
may 31, 2026