Las migraciones son la forma en que Django propaga los cambios que realiza en sus modelos (agregar un campo, eliminar un modelo, etc.) en el esquema de su base de datos. Están diseñados para ser en su mayoría automáticos, pero necesitará saber cuándo realizar migraciones, cuándo ejecutarlas y los problemas comunes que puede encontrar.

Los comandos

Hay varios comandos que usará para interactuar con las migraciones y el manejo de Django del esquema de la base de datos:

  • migrate, que se encarga de aplicar y no aplicar las migraciones.
  • makemigrations, que se encarga de crear nuevas migraciones basadas en los cambios que ha realizado en sus modelos.
  • sqlmigrate, que muestra las sentencias SQL para una migración.
  • showmigrations, que enumera las migraciones de un proyecto y su estado.

Debe pensar en las migraciones como un sistema de control de versiones para el esquema de su base de datos. makemigrations es responsable de empaquetar los cambios de su modelo en archivos de migración individuales, de forma análoga a las confirmaciones, y migrate es responsable de aplicarlos a su base de datos.

Los archivos de migración de cada aplicación se encuentran en un directorio de “migraciones” dentro de esa aplicación, y están diseñados para comprometerse y distribuirse como parte de su base de código. Debería hacerlos una vez en su máquina de desarrollo y luego ejecutar las mismas migraciones en las máquinas de sus colegas, sus máquinas de preparación y, finalmente, sus máquinas de producción.

Nota

Es posible anular el nombre del paquete que contiene las migraciones por aplicación modificando el 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 ve en el desarrollo y la puesta en escena es, bajo las mismas circunstancias, exactamente lo que sucederá en producción.

Django realizará migraciones para cualquier cambio en sus modelos o campos, incluso las 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 el historial, y es posible que necesite esas opciones en algunas migraciones de datos más adelante (por ejemplo, si ha configurado validadores personalizados).

Soporte de backend

Las migraciones se admiten en todos los backends con los que se envía Django, así como en los backends de terceros si se han programado para admitir la alteración del esquema (realizado a través del SchemaEditor clase).

Sin embargo, algunas bases de datos son más capaces que otras cuando se trata de migraciones de esquemas; algunas de las advertencias se tratan a continuación.

PostgreSQL

PostgreSQL es la más capaz de todas las bases de datos aquí en términos de soporte de esquemas.

La única advertencia es que antes de PostgreSQL 11, agregar columnas con valores predeterminados provoca una reescritura completa de la tabla, durante un tiempo proporcional a su tamaño. Por esta razón, se recomienda que siempre cree nuevas columnas con null=True, ya que de esta manera se agregarán de inmediato.

MySQL

MySQL carece de soporte para transacciones relacionadas con operaciones de alteración de esquemas, lo que significa que si una migración no se aplica, tendrá que anular manualmente los cambios para volver a intentarlo (es imposible retroceder a un punto anterior).

Además, MySQL reescribirá completamente las tablas para casi todas las operaciones de esquema y, por lo general, lleva un tiempo proporcional al número de filas de la tabla para agregar o eliminar columnas. En hardware más lento, esto puede ser peor que un minuto por millón de filas; agregar algunas columnas a una tabla con solo unos pocos millones de filas podría bloquear su sitio durante más de diez minutos.

Finalmente, MySQL tiene límites relativamente pequeños en la longitud de los nombres para columnas, tablas e índices, así como un límite en el tamaño combinado de todas las columnas que cubre un índice. Esto significa que los índices que son posibles en otros backends no se podrán crear en MySQL.

SQLite

SQLite tiene muy poco soporte de alteración de esquema incorporado, por lo que Django intenta emularlo de la siguiente manera:

  • Creando una nueva tabla con el nuevo esquema
  • Copiar los datos en
  • Dejando caer la mesa vieja
  • Cambiar el nombre de la nueva tabla para que coincida con el nombre original

Este proceso generalmente funciona bien, pero puede ser lento y ocasionalmente con errores. No se recomienda que ejecute y migre SQLite en un entorno de producción a menos que esté muy consciente de los riesgos y sus limitaciones; el soporte con el que viene Django está diseñado para permitir a los desarrolladores usar SQLite en sus máquinas locales para desarrollar proyectos de Django menos complejos sin la necesidad de una base de datos completa.

Flujo de trabajo

Django puede crear migraciones por ti. Realice cambios en sus modelos, por ejemplo, agregue un campo y elimine un modelo, y luego ejecute makemigrations:

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0003_auto.py:
    - Alter field author on book

Sus modelos serán escaneados y comparados con las versiones contenidas actualmente en sus archivos de migración, y luego se escribirá un nuevo conjunto de migraciones. Asegúrese de leer la salida para ver qué makemigrations cree que ha cambiado; no es perfecto y, en el caso de cambios complejos, es posible que no detecte lo que esperaba.

Una vez que tenga sus nuevos archivos de migración, debe aplicarlos a su base de datos para asegurarse 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 se aplica la migración, confirma la migración y los modelos cambian a tu sistema de control de versiones como una única confirmación; de esa manera, cuando otros desarrolladores (o tus servidores de producción) revisan el código, obtendrán ambos cambios en tus modelos. y la migración acompañante al mismo tiempo.

Si desea dar a las migraciones un nombre significativo en lugar de uno generado, puede usar el makemigrations --name opción:

$ python manage.py makemigrations --name changed_my_model your_app_label

Control de versiones

Debido a que las migraciones se almacenan en el control de versiones, ocasionalmente se encontrará con situaciones en las que usted y otro desarrollador han realizado una migración a la misma aplicación al mismo tiempo, lo que resulta en dos migraciones con el mismo número.

No se preocupe, los números están ahí para referencia de los desarrolladores, Django solo se preocupa de que cada migración tenga un nombre diferente. Las migraciones especifican de qué otras migraciones dependen, incluidas las migraciones anteriores en la misma aplicación, en el archivo, por lo que es posible detectar cuándo hay dos nuevas migraciones para la misma aplicación que no están ordenadas.

Cuando esto suceda, Django le preguntará y le dará algunas opciones. Si cree que es lo suficientemente seguro, le ofrecerá la linealización automática de las dos migraciones. De lo contrario, tendrá que ingresar y modificar las migraciones usted mismo; no se preocupe, esto no es difícil y se explica más en Archivos de migración debajo.

Actas

En las bases de datos que admiten transacciones DDL (SQLite y PostgreSQL), todas las operaciones de migración se ejecutarán dentro de una única transacción de forma predeterminada. Por el contrario, si una base de datos no admite transacciones DDL (por ejemplo, MySQL, Oracle), todas las operaciones se ejecutarán sin una transacción.

Puede evitar que una migración se ejecute en una transacción configurando el atomic atribuir 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 usando atomic() o pasando atomic=True para RunPython. Ver Migraciones no atómicas para más detalles.

Dependencias

Si bien las migraciones son por aplicación, las tablas y relaciones implícitas en sus modelos son demasiado complejas para crearlas para una aplicación a la vez. Cuando realiza una migración que requiere algo más para ejecutarse, por ejemplo, agrega un ForeignKey en tus books aplicación a tu authors aplicación: la migración resultante contendrá una dependencia de una migración en authors.

Esto significa que cuando ejecuta las migraciones, el authors la migración se ejecuta primero y crea la tabla ForeignKey referencias, y luego la migración que hace que el ForeignKey La columna se ejecuta después y crea la restricción. Si esto no sucediera, la migración intentaría crear el ForeignKey columna sin la tabla a la que hace referencia existente y su base de datos arrojaría un error.

Este comportamiento de dependencia afecta a la mayoría de las operaciones de migración en las que se restringe 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 deba usarse para corregir las dependencias será.

Las aplicaciones sin migraciones no deben tener relaciones (ForeignKey, ManyToManyField, etc.) a aplicaciones con migraciones. A veces puede funcionar, pero no es compatible.

Archivos de migración

Las migraciones se almacenan como un formato en disco, denominado aquí “archivos de migración”. Estos archivos son en realidad archivos Python normales con un diseño de objeto acordado, escritos en un estilo declarativo.

Un archivo de migración básico tiene este aspecto:

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 llamado Migration. Luego inspecciona este objeto en busca de cuatro atributos, de los cuales solo dos se utilizan la mayor parte del tiempo:

  • dependencies, una lista de migraciones de la que depende.
  • operations, una lista de Operation clases que definen lo que hace esta migración.

Las operaciones son la clave; son un conjunto de instrucciones declarativas que le dicen a Django qué cambios de esquema deben realizarse. Django los escanea y crea una representación en memoria de todos los cambios de esquema en todas las aplicaciones, y lo usa para generar el SQL que hace los cambios de esquema.

Esa estructura en memoria también se usa para averiguar cuáles son las diferencias entre sus modelos y el estado actual de sus migraciones; Django ejecuta todos los cambios, en orden, en un conjunto de modelos en memoria para generar el estado de sus modelos la última vez que ejecutó makemigrations. A continuación, utiliza estos modelos para compararlos con los de su models.py archivos para averiguar qué ha cambiado.

Rara vez, si es que alguna vez, necesita editar los archivos de migración a mano, pero es totalmente posible escribirlos manualmente si es necesario. Algunas de las operaciones más complejas no son autodetectables y solo están disponibles a través de una migración escrita a mano, así que no tenga miedo de editarlas si es necesario.

Campos Personalizados

No puede modificar el número de argumentos posicionales en un campo personalizado ya migrado sin generar un TypeError. La antigua migración llamará a la modificada __init__ método con la antigua firma. Entonces, si necesita un nuevo argumento, cree un argumento de palabra clave y agregue algo como assert 'argument_name' in kwargs en el constructor.

Gestores de modelos

Opcionalmente, puede serializar administradores en migraciones y tenerlos disponibles en RunPython operaciones. Esto se hace definiendo un use_in_migrations atributo en la clase de administrador:

class MyManager(models.Manager):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Si está utilizando el from_queryset() función para generar dinámicamente una clase de administrador, debe heredar de la clase generada para que sea importable:

class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Consulte las notas sobre Modelos históricos en migraciones para ver las implicaciones que vienen.

Migraciones iniciales

Migration.initial

Las “migraciones iniciales” de una aplicación son las migraciones que crean la primera versión de las tablas de esa aplicación. Por lo general, una aplicación tendrá una migración inicial, pero en algunos casos de interdependencias de modelos complejas, puede tener dos o más.

Las migraciones iniciales están marcadas con un initial = True atributo de clase en la clase de migración. Si una initial no se encuentra el atributo de clase, una migración se considerará “inicial” si es la primera migración en el aplicación (es decir, si no depende de ninguna otra migración en la misma aplicación).

Cuando el migrate --fake-initial Si se utiliza esta opción, estas migraciones iniciales se tratan de forma especial. Para una migración inicial que crea una o más tablas (CreateModel operación), Django verifica que todas esas tablas ya existan en la base de datos y, de ser así, aplica falsamente la migración. Del mismo modo, para una migración inicial que agrega uno o más campos (AddField operación), Django verifica que todas las columnas respectivas ya existan en la base de datos y, de ser así, aplica falsamente la migración. Sin --fake-initial, las migraciones iniciales no se tratan de manera diferente a cualquier otra migración.

Consistencia de la historia

Como se mencionó anteriormente, es posible que deba linealizar las migraciones manualmente cuando se unen dos ramas de desarrollo. Mientras edita las dependencias de migración, puede crear inadvertidamente un estado de historial incoherente en el que se haya aplicado una migración, pero algunas de sus dependencias no. Esta es una fuerte indicación de que las dependencias son incorrectas, por lo que Django se negará a ejecutar migraciones o realizar nuevas migraciones hasta que se solucione. Al utilizar varias bases de datos, puede utilizar el allow_migrate() método de enrutadores de base de datos para controlar que bases de datos makemigrations comprueba el historial coherente.

Agregar migraciones a aplicaciones

Las nuevas aplicaciones vienen preconfiguradas para aceptar migraciones, por lo que puede agregar migraciones ejecutando makemigrations una vez que haya realizado algunos cambios.

Si su aplicación ya tiene modelos y tablas de base de datos, y aún no tiene migraciones (por ejemplo, la creó con una versión anterior de Django), deberá convertirla para usar migraciones ejecutando:

$ python manage.py makemigrations your_app_label

Esto hará una nueva migración inicial para su aplicación. Ahora, corre python
manage.py migrate --fake-initial
, y Django detectará que tienes una migración inicial y que las tablas que desea crear ya existen y marcará la migración como ya aplicada. (Sin el migrate
--fake-initial
, el comando generaría un error porque las tablas que desea crear ya existen).

Tenga en cuenta que esto solo funciona dadas dos cosas:

  • No has cambiado tus modelos desde que hiciste sus tablas. Para que las migraciones funcionen, debe realizar la migración inicial primero y luego hacer cambios, ya que Django compara los cambios con los archivos de migración, no con la base de datos.
  • No ha editado manualmente su base de datos: Django no podrá detectar que su base de datos no coincide con sus modelos, solo obtendrá errores cuando las migraciones intenten modificar esas tablas.

Inversión de migraciones

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
...> py 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 desea revertir todas las migraciones aplicadas para una aplicación, use 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
...> py 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. Intentar revertir tales migraciones aumentará 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
...> py 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

Modelos históricos

Cuando ejecuta migraciones, Django trabaja a partir de versiones históricas de sus modelos almacenados en los archivos de migración. Si escribe código Python usando el RunPython operación, o si tiene allow_migrate métodos en los enrutadores de su base de datos, necesitará usar estas versiones del modelo histórico en lugar de importarlas directamente.

Advertencia

Si importa modelos directamente en lugar de utilizar los modelos históricos, sus migraciones puede funcionar inicialmente pero fallará en el futuro cuando intente volver a ejecutar migraciones antiguas (comúnmente, cuando configura una nueva instalación y ejecuta todas las migraciones para configurar la base de datos).

Esto significa que los problemas del modelo histórico pueden no ser inmediatamente obvios. Si se encuentra con este tipo de falla, está bien editar la migración para usar los modelos históricos en lugar de las importaciones directas y confirmar esos cambios.

Debido a que es imposible serializar código Python arbitrario, estos modelos históricos no tendrán ningún método personalizado que haya definido. Sin embargo, tendrán los mismos campos, relaciones, gerentes (limitados a aquellos con use_in_migrations = True) y Meta opciones (también versionadas, por lo que pueden ser diferentes de las actuales).

Advertencia

Esto significa que NO tendrá personalización save() métodos llamados en objetos cuando acceda a ellos en migraciones, y NO tendrá constructores personalizados o métodos de instancia. ¡Planifique adecuadamente!

Referencias a funciones en opciones de campo como upload_to y limit_choices_to y declaraciones del administrador del modelo con los gerentes que tienen use_in_migrations = True se serializan en las migraciones, por lo que las funciones y clases deberán mantenerse mientras haya una migración que haga referencia a ellas. Alguna campos de modelo personalizados también será necesario conservarlos, ya que estos se importan directamente mediante migraciones.

Además, las clases base concretas del modelo se almacenan como punteros, por lo que siempre debe mantener las clases base mientras haya una migración que contenga una referencia a ellas. En el lado positivo, los métodos y administradores de estas clases base heredan normalmente, por lo que si es absolutamente necesario acceder a ellos, puede optar por moverlos a una superclase.

Para eliminar referencias antiguas, puede migraciones de calabaza o, si no hay muchas referencias, cópielas en los archivos de migración.

Consideraciones al eliminar campos de modelo

De manera similar a las consideraciones de “referencias a funciones históricas” descritas en la sección anterior, la eliminación de campos de modelo personalizados de su proyecto o aplicación de terceros causará un problema si se hace referencia a ellos en migraciones antiguas.

Para ayudar con esta situación, Django proporciona algunos atributos de campo del modelo para ayudar con la depreciación del campo del modelo usando el marco de verificación del sistema.

Añade el system_check_deprecated_details atributo a su campo modelo similar al siguiente:

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 desactivación de su elección (dos o tres lanzamientos de funciones para los campos en el propio Django), cambie el system_check_deprecated_details atribuir a system_check_removed_details y actualice 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.
    }

Debe mantener los métodos del campo que son necesarios para que funcione en migraciones de bases de datos como __init__(), deconstruct(), y get_internal_type(). Mantenga este campo de código auxiliar durante el tiempo que existan migraciones que hagan referencia al campo. Por ejemplo, después de aplastar las migraciones y eliminar las antiguas, debería poder eliminar el campo por completo.

Migraciones de datos

Además de cambiar el esquema de la base de datos, también puede utilizar las migraciones para cambiar los datos en la propia base de datos, junto con el esquema si lo desea.

Las migraciones que alteran los datos generalmente se denominan “migraciones de datos”; es mejor escribirlas como migraciones separadas, junto con las migraciones de su esquema.

Django no puede generar migraciones de datos automáticamente, como lo hace con las migraciones de esquemas, pero no es muy difícil escribirlas. Los archivos de migración en Django se componen de Operaciones, y la operación principal que utiliza para las migraciones de datos es RunPython.

Para comenzar, cree un archivo de migración vacío desde el que pueda trabajar (Django colocará el archivo en el lugar correcto, sugerirá un nombre y agregará dependencias por usted):

python manage.py makemigrations --empty yourappname

Luego, abra el archivo; debería verse algo como 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, todo lo que necesita hacer es crear una nueva función y tener RunPython úselo. RunPython espera un invocable como argumento que toma dos argumentos: el primero es un registro de aplicaciones que tiene las versiones históricas de todos sus modelos cargados para que coincidan con el lugar de su historial en el que se encuentra la migración, y el segundo es un SchemaEditor, que puede usar para efectuar manualmente cambios en el esquema de la base de datos (pero tenga cuidado, ¡hacer esto puede confundir al autodetector de migración!)

Escribamos una migración que pueble nuestro nuevo name campo con los valores combinados de first_name y last_name (hemos recobrado el sentido y nos hemos dado cuenta de que no todo el mundo tiene nombre y apellido). Todo lo que tenemos que hacer es usar 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 = '%s %s' % (person.first_name, person.last_name)
        person.save()

class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(combine_names),
    ]

Una vez hecho esto, podemos correr python manage.py migrate como de costumbre y la migración de datos se ejecutará junto con otras migraciones.

Puede pasar una segunda llamada a RunPython para ejecutar cualquier lógica que desee que se ejecute al migrar hacia atrás. Si se omite este invocable, la migración hacia atrás generará una excepción.

Acceder a modelos desde otras aplicaciones

Al escribir un RunPython función que utiliza modelos de aplicaciones distintas de aquella en la que se encuentra la migración, la migración dependencies El atributo 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 intentas recuperar el modelo en el RunPython función usando apps.get_model().

En el siguiente ejemplo, tenemos una migración en app1 que necesita usar modelos en app2. No nos preocupan los detalles de move_m1 aparte del hecho de que necesitará acceder a los 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),
    ]

Migraciones más avanzadas

Si está interesado en las operaciones de migración más avanzadas o desea poder escribir las suyas propias, consulte la referencia de operaciones de migración y el “cómo” en escribir migraciones.

Aplastando migraciones

Se le anima a realizar migraciones libremente y no preocuparse por cuántas tiene; el código de migración está optimizado para hacer frente a cientos a la vez sin mucha ralentización. Sin embargo, eventualmente querrá pasar de tener varios cientos de migraciones a solo unas pocas, y ahí es donde entra el aplastamiento.

El aplastamiento es el acto de reducir un conjunto existente de muchas migraciones a una (oa veces algunas) migraciones que aún representan los mismos cambios.

Django hace esto tomando todas sus migraciones existentes, extrayendo sus Operationsy ponerlos todos en secuencia, y luego ejecutar un optimizador sobre ellos para intentar reducir la longitud de la lista; por ejemplo, sabe que CreateModel y DeleteModel se cancelan mutuamente, y sabe que AddField se puede enrollar en CreateModel.

Una vez que la secuencia de operación se ha reducido tanto como sea posible, la cantidad posible depende de cuán estrechamente entrelazados estén sus modelos y si tiene alguno. RunSQL o RunPython operaciones (que no se pueden optimizar a menos que estén marcadas como elidable) – Django lo volverá a escribir en un nuevo conjunto de archivos de migración.

Estos archivos están marcados para decir que reemplazan las migraciones previamente aplastadas, por lo que pueden coexistir con los archivos de migración antiguos, y Django cambiará inteligentemente entre ellos dependiendo de dónde se encuentre en el historial. Si todavía se encuentra en la mitad del conjunto de migraciones que aplastó, seguirá usándolas hasta que llegue al final y luego cambiará al historial aplastado, mientras que las nuevas instalaciones usarán la nueva migración aplastada y omitirán todas las antiguas. .

Esto le permite aplastar y no estropear los sistemas actualmente en producción que aún no están completamente actualizados. El proceso recomendado es aplastar, mantener los archivos antiguos, confirmar y publicar, esperar hasta que todos los sistemas se actualicen con la nueva versión (o si es un proyecto de terceros, asegúrese de que sus usuarios actualicen las versiones en orden sin omitir ninguna), y luego elimine los archivos antiguos, confirme y realice una segunda versión.

El comando que respalda todo esto es squashmigrations – pásele la etiqueta de la aplicación y el nombre de la migración que desea aplastar, y comenzará a funcionar:

$ ./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? [yN] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_somthing.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.

Utilizar el squashmigrations --squashed-name opción si desea establecer el nombre de la migración aplastada en lugar de utilizar una autogenerada.

Tenga en cuenta que las interdependencias de modelos en Django pueden volverse muy complejas, y el aplastamiento puede resultar en migraciones que no se ejecutan; o mal optimizado (en cuyo caso puede intentarlo de nuevo con --no-optimize, aunque también debe informar un problema), o con un CircularDependencyError, en cuyo caso puede resolverlo manualmente.

Para resolver manualmente un CircularDependencyError, divida una de las ForeignKeys en el ciclo de dependencia circular en una migración separada y mueva la dependencia en la otra aplicación con ella. Si no está seguro, vea cómo makemigrations se ocupa del problema cuando se le pide que cree nuevas migraciones a partir de sus modelos. En una versión futura de Django, squashmigrations se actualizará para intentar resolver estos errores por sí mismo.

Una vez que haya aplastado su migración, debe confirmarla junto con las migraciones que reemplaza y distribuir este cambio a todas las instancias en ejecución de su aplicación, asegurándose de que se ejecuten migrate para almacenar el cambio en su base de datos.

A continuación, debe realizar la transición de la migración aplastada a una migración normal mediante:

  • Eliminando todos los archivos de migración que reemplaza.
  • Actualizar todas las migraciones que dependen de las migraciones eliminadas para que dependan de la migración aplastada.
  • La eliminación de la replaces atributo en el Migration clase de la migración aplastada (así es como Django dice que es una migración aplastada).

Nota

Una vez que haya aplastado una migración, no debe volver a aplastar esa migración aplastada hasta que haya realizado la transición completa a una migración normal.

Serializar valores

Las migraciones son archivos de Python que contienen las definiciones antiguas de sus modelos; por lo tanto, para escribirlos, Django debe tomar el estado actual de sus 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 un estándar de Python sobre cómo un valor se puede convertir de nuevo en código (repr() solo funciona para valores básicos y no especifica rutas de importación).

Django puede serializar lo siguiente:

  • int, float, bool, str, bytes, None, NoneType
  • list, set, tuple, dict, range.
  • datetime.date, datetime.time, y datetime.datetime instancias (incluye aquellas que son conscientes de la zona horaria)
  • decimal.Decimal instancias
  • enum.Enum instancias
  • uuid.UUID instancias
  • functools.partial() y functools.partialmethod instancias que tienen serializable func, args, y keywords valores.
  • Objetos de ruta puros y concretos de pathlib. Las trayectorias concretas se convierten en su equivalente de trayectoria pura, p. Ej. pathlib.PosixPath para pathlib.PurePosixPath.
  • os.PathLike instancias, por ejemplo os.DirEntry, que se convierten en str o bytes utilizando os.fspath().
  • LazyObject instancias que envuelven un valor serializable.
  • Tipos de enumeración (p. Ej. TextChoices o IntegerChoices) instancias.
  • Cualquier campo de Django
  • Cualquier función o referencia de método (p. Ej. datetime.datetime.today) (debe estar en el alcance de nivel superior del módulo)
  • Métodos independientes que se utilizan desde el cuerpo de la clase.
  • Cualquier referencia de clase (debe estar en el alcance de nivel superior del módulo)
  • Cualquier cosa con una costumbre deconstruct() métodovea abajo)

Cambiado en Django 3.2:

Soporte de serialización para objetos de ruta puros y concretos de pathlib, y os.PathLike Se agregaron instancias.

Django no puede serializar:

  • Clases anidadas
  • Instancias de clases arbitrarias (p. Ej. MyClass(4.3, 5.7))
  • Lambdas

Serializadores personalizados

Puede serializar otros tipos escribiendo un serializador personalizado. Por ejemplo, si Django no serializó Decimal de forma predeterminada, puede 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 usar el serializador.

los serialize() El método de su serializador debe devolver una cadena de cómo debería aparecer el valor en las migraciones y un conjunto de las importaciones que se necesitan en la migración.

Añadiendo un deconstruct() método

Puede dejar que Django serialice sus propias instancias de clases personalizadas dando a la clase un deconstruct() método. No necesita argumentos y debería devolver una tupla de tres cosas (path, args, kwargs):

  • path debe ser la ruta de Python a la clase, con el nombre de la clase incluido como última parte (por ejemplo, myapp.custom_things.MyClass). Si su clase no está disponible en el nivel superior de un módulo, no se puede serializar.
  • args debe ser una lista de argumentos posicionales para pasar a su clase ‘ __init__ método. Todo en esta lista debería ser serializable.
  • kwargs debería ser un dictado de argumentos de palabras clave para pasar a su clase ‘ __init__ método. Cada valor debe ser serializable en sí mismo.

Nota

Este valor de retorno es diferente del deconstruct() método para campos personalizados que devuelve una tupla de cuatro elementos.

Django escribirá el valor como una instanciación de su clase con los argumentos dados, similar a la forma en que escribe las referencias a los campos de Django.

Para evitar que se cree una nueva migración cada vez makemigrations se ejecuta, también debe agregar un __eq__() método a la clase decorada. Esta función será llamada por el marco de migración de Django para detectar cambios entre estados.

Siempre que todos los argumentos del constructor de su clase sean serializables, puede usar el @deconstructible decorador de clase de django.utils.deconstruct para agregar el deconstruct() método:

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 su constructor, y luego devuelve esos argumentos exactamente cuando se llama a deconstruct ().

Soporta múltiples versiones de Django

Si eres el responsable de mantenimiento de una aplicación de terceros con modelos, es posible que debas enviar migraciones que admitan varias versiones de Django. En este caso, siempre debe ejecutar makemigrations con la versión más baja de Django que desee admitir.

El sistema de migraciones mantendrá la compatibilidad con versiones anteriores de acuerdo con la misma política que el resto de Django, por lo que los archivos de migración generados en Django XY deberían ejecutarse sin cambios en Django X.Y + 1. Sin embargo, el sistema de migraciones no promete compatibilidad con versiones posteriores. Es posible que se agreguen nuevas funciones y que los archivos de migración generados con versiones más recientes de Django no funcionen en versiones anteriores.

Ver también

La referencia de operaciones de migración
Cubre la API de operaciones de esquema, las operaciones especiales y la escritura de sus propias operaciones.
El “cómo” de las migraciones de escritura
Explica cómo estructurar y escribir migraciones de bases de datos para diferentes escenarios que pueda encontrar.