sql >> Base de Datos >  >> RDS >> Database

Profundizando en las migraciones de Django

Este es el segundo artículo de nuestra serie de migraciones de Django:

  • Parte 1:Migraciones de Django:Introducción
  • Parte 2:profundizar en las migraciones de Django (artículo actual)
  • Parte 3:Migraciones de datos
  • Video:Migraciones de Django 1.7 - Introducción

En el artículo anterior de esta serie, aprendió sobre el propósito de las migraciones de Django. Se ha familiarizado con los patrones de uso fundamentales, como la creación y aplicación de migraciones. Ahora es el momento de profundizar en el sistema de migración y echar un vistazo a algunos de sus mecanismos subyacentes.

Al final de este artículo, sabrá:

  • Cómo Django realiza un seguimiento de las migraciones
  • Cómo saben las migraciones qué operaciones de base de datos deben realizar
  • Cómo se definen las dependencias entre migraciones

Una vez que haya comprendido esta parte del sistema de migración de Django, estará bien preparado para crear sus propias migraciones personalizadas. ¡Saltamos justo donde lo dejamos!

Este artículo utiliza el bitcoin_tracker Proyecto Django construido en Django Migrations:A Primer. Puede volver a crear ese proyecto trabajando en ese artículo o puede descargar el código fuente:

Descargar código fuente: Haga clic aquí para descargar el código del proyecto de migraciones de Django que usará en este artículo.


Cómo sabe Django qué migraciones aplicar

Recapitulemos el último paso del artículo anterior de la serie. Creó una migración y luego aplicó todas las migraciones disponibles con python manage.py migrate .Si ese comando se ejecutó con éxito, entonces las tablas de su base de datos ahora coinciden con las definiciones de su modelo.

¿Qué pasa si vuelves a ejecutar ese comando? Probémoslo:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
  No migrations to apply.

¡No pasó nada! Una vez que se ha aplicado una migración a una base de datos, Django no volverá a aplicar esta migración a esa base de datos en particular. Asegurarse de que una migración se aplique solo una vez requiere realizar un seguimiento de las migraciones que se han aplicado.

Django usa una tabla de base de datos llamada django_migrations . Django crea automáticamente esta tabla en su base de datos la primera vez que aplica cualquier migración. Por cada migración que se aplica o falsifica, se inserta una nueva fila en la tabla.

Por ejemplo, así es como se ve esta tabla en nuestro bitcoin_tracker proyecto:

ID Aplicación Nombre Aplicado
1 contenttypes 0001_initial 2019-02-05 20:23:21.461496
2 auth 0001_initial 2019-02-05 20:23:21.489948
3 admin 0001_initial 2019-02-05 20:23:21.508742
4 admin 0002_logentry_remove... 2019-02-05 20:23:21.531390
5 admin 0003_logentry_add_ac... 2019-02-05 20:23:21.564834
6 contenttypes 0002_remove_content_... 2019-02-05 20:23:21.597186
7 auth 0002_alter_permissio... 2019-02-05 20:23:21.608705
8 auth 0003_alter_user_emai... 2019-02-05 20:23:21.628441
9 auth 0004_alter_user_user... 2019-02-05 20:23:21.646824
10 auth 0005_alter_user_last... 2019-02-05 20:23:21.661182
11 auth 0006_require_content... 2019-02-05 20:23:21.663664
12 auth 0007_alter_validator... 2019-02-05 20:23:21.679482
13 auth 0008_alter_user_user... 2019-02-05 20:23:21.699201
14 auth 0009_alter_user_last... 2019-02-05 20:23:21.718652
15 historical_data 0001_initial 2019-02-05 20:23:21.726000
16 sessions 0001_initial 2019-02-05 20:23:21.734611
19 historical_data 0002_switch_to_decimals 2019-02-05 20:30:11.337894

Como puede ver, hay una entrada para cada migración aplicada. La tabla no solo contiene las migraciones de nuestros historical_data aplicación, sino también las migraciones de todas las demás aplicaciones instaladas.

La próxima vez que se ejecuten las migraciones, Django omitirá las migraciones enumeradas en la tabla de la base de datos. Esto significa que, incluso si cambia manualmente el archivo de una migración que ya se ha aplicado, Django ignorará estos cambios, siempre que ya haya una entrada para ello en la base de datos.

Puede engañar a Django para que vuelva a ejecutar una migración eliminando la fila correspondiente de la tabla, pero esto rara vez es una buena idea y puede dejarlo con un sistema de migración roto.



El archivo de migración

Qué sucede cuando ejecuta python manage.py makemigrations <appname> ? Django busca cambios realizados en los modelos de su aplicación <appname> . Si encuentra alguno, como un modelo que se ha agregado, crea un archivo de migración en migrations subdirectorio. Este archivo de migración contiene una lista de operaciones para sincronizar el esquema de su base de datos con la definición de su modelo.

Nota: Su aplicación debe aparecer en la lista de INSTALLED_APPS y debe contener un migrations directorio con un __init__.py expediente. De lo contrario, Django no creará ninguna migración para él.

Las migrations El directorio se crea automáticamente cuando crea una nueva aplicación con startapp comando de administración, pero es fácil olvidarlo al crear una aplicación manualmente.

Los archivos de migración son solo Python, así que echemos un vistazo al primer archivo de migración en historical_prices aplicación Puede encontrarlo en historical_prices/migrations/0001_initial.py . Debería ser algo como esto:

from django.db import models, migrations

class Migration(migrations.Migration):
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='PriceHistory',
            fields=[
                ('id', models.AutoField(
                    verbose_name='ID',
                    serialize=False,
                    primary_key=True,
                    auto_created=True)),
                ('date', models.DateTimeField(auto_now_add=True)),
                ('price', models.DecimalField(decimal_places=2, max_digits=5)),
                ('volume', models.PositiveIntegerField()),
                ('total_btc', models.PositiveIntegerField()),
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]

Como puede ver, contiene una sola clase llamada Migration que hereda de django.db.migrations.Migration . Esta es la clase que el framework de migración buscará y ejecutará cuando le pida que aplique migraciones.

La Migration class contiene dos listas principales:

  1. dependencies
  2. operations

Operaciones de Migración

Veamos las operations lista primero. Esta tabla contiene las operaciones que se van a realizar como parte de la migración. Las operaciones son subclases de la clase django.db.migrations.operations.base.Operation . Estas son las operaciones comunes integradas en Django:

Clase de operación Descripción
CreateModel Crea un nuevo modelo y la tabla de base de datos correspondiente
DeleteModel Elimina un modelo y descarta su tabla de base de datos
RenameModel Cambia el nombre de un modelo y cambia el nombre de su tabla de base de datos
AlterModelTable Cambia el nombre de la tabla de la base de datos para un modelo
AlterUniqueTogether Cambia las restricciones únicas de un modelo
AlterIndexTogether Cambia los índices de un modelo
AlterOrderWithRespectTo Crea o elimina el _order columna para un modelo
AlterModelOptions Cambia varias opciones del modelo sin afectar la base de datos
AlterModelManagers Cambia los administradores disponibles durante las migraciones
AddField Agrega un campo a un modelo y la columna correspondiente en la base de datos
RemoveField Elimina un campo de un modelo y elimina la columna correspondiente de la base de datos
AlterField Cambia la definición de un campo y altera su columna de base de datos si es necesario
RenameField Renombra un campo y, si es necesario, también su columna de base de datos
AddIndex Crea un índice en la tabla de la base de datos para el modelo
RemoveIndex Elimina un índice de la tabla de la base de datos para el modelo

Tenga en cuenta que las operaciones se nombran según los cambios realizados en las definiciones del modelo, no las acciones que se realizan en la base de datos. Cuando aplica una migración, cada operación es responsable de generar las declaraciones SQL necesarias para su base de datos específica. Por ejemplo, CreateModel generaría un CREATE TABLE Sentencia SQL.

Fuera de la caja, las migraciones tienen soporte para todas las bases de datos estándar que admite Django. Entonces, si se apega a las operaciones enumeradas aquí, puede hacer más o menos cambios en sus modelos que desee, sin tener que preocuparse por el SQL subyacente. Eso está hecho por ti.

Nota: En algunos casos, es posible que Django no detecte correctamente sus cambios. Si cambia el nombre de un modelo y cambia varios de sus campos, Django podría confundirlo con un nuevo modelo.

En lugar de un RenameModel y varios AlterField operaciones, creará un DeleteModel y un CreateModel operación. En lugar de cambiar el nombre de la tabla de la base de datos para el modelo, la soltará y creará una nueva tabla con el nuevo nombre, eliminando efectivamente todos sus datos.

Acostúmbrese a verificar las migraciones generadas y pruébelas en una copia de su base de datos antes de ejecutarlas en datos de producción.

Django proporciona tres clases de operaciones más para casos de uso avanzado:

  1. RunSQL le permite ejecutar SQL personalizado en la base de datos.
  2. RunPython le permite ejecutar cualquier código de Python.
  3. SeparateDatabaseAndState es una operación especializada para usos avanzados.

Con estas operaciones, básicamente puede hacer cualquier cambio que desee en su base de datos. Sin embargo, no encontrará estas operaciones en una migración que se haya creado automáticamente con makemigrations comando de gestión.

Desde Django 2.0, también hay un par de operaciones específicas de PostgreSQL disponibles en django.contrib.postgres.operations que puede usar para instalar varias extensiones de PostgreSQL:

  • BtreeGinExtension
  • BtreeGistExtension
  • CITextExtension
  • CryptoExtension
  • HStoreExtension
  • TrigramExtension
  • UnaccentExtension

Tenga en cuenta que una migración que contenga una de estas operaciones requiere un usuario de la base de datos con privilegios de superusuario.

Por último, pero no menos importante, también puede crear sus propias clases de operaciones. Si desea investigar eso, eche un vistazo a la documentación de Django sobre la creación de operaciones de migración personalizadas.



Dependencias de migración

Las dependencies La lista en una clase de migración contiene las migraciones que se deben aplicar antes de que se pueda aplicar esta migración.

En el 0001_initial.py migración que vio anteriormente, no se debe aplicar nada antes, por lo que no hay dependencias. Echemos un vistazo a la segunda migración en historical_prices aplicación En el archivo 0002_switch_to_decimals.py , las dependencies atributo de Migration tiene una entrada:

from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('historical_data', '0001_initial'),
    ]
    operations = [
        migrations.AlterField(
            model_name='pricehistory',
            name='volume',
            field=models.DecimalField(decimal_places=3, max_digits=7),
        ),
    ]

La dependencia anterior dice que la migración 0001_initial de la aplicación historical_data debe ejecutarse primero. Eso tiene sentido, porque la migración 0001_initial crea la tabla que contiene el campo que la migración 0002_switch_to_decimals quiere cambiar.

Una migración también puede depender de una migración desde otra aplicación, como esta:

class Migration(migrations.Migration):
    ...

    dependencies = [
        ('auth', '0009_alter_user_last_name_max_length'),
    ]

Esto suele ser necesario si un modelo tiene una clave externa que apunta a un modelo en otra aplicación.

Alternativamente, también puede exigir que se ejecute una migración antes otra migración usando el atributo run_before :

class Migration(migrations.Migration):
    ...

    run_before = [
        ('third_party_app', '0001_initial'),
    ]

Las dependencias también se pueden combinar para que pueda tener varias dependencias. Esta funcionalidad brinda mucha flexibilidad, ya que puede acomodar claves externas que dependen de modelos de diferentes aplicaciones.

La opción de definir explícitamente dependencias entre migraciones también significa que la numeración de las migraciones (generalmente 0001 , 0002 , 0003 , …) no representa estrictamente el orden en que se aplican las migraciones. Puedes agregar cualquier dependencia que desees y así controlar el orden sin tener que volver a numerar todas las migraciones.



Ver la migración

Por lo general, no tiene que preocuparse por el SQL que generan las migraciones. Pero si desea volver a verificar que el SQL generado tiene sentido o simplemente tiene curiosidad por su apariencia, entonces Django lo tiene cubierto con sqlmigrate comando de gestión:

$ python manage.py sqlmigrate historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
CREATE TABLE "historical_data_pricehistory" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "date" datetime NOT NULL,
    "price" decimal NOT NULL,
    "volume" integer unsigned NOT NULL
);
COMMIT;

Al hacerlo, se enumerarán las consultas SQL subyacentes que generará la migración especificada, en función de la base de datos en su settings.py expediente. Cuando pasa el parámetro --backwards , Django genera el SQL para no aplicar la migración:

$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;

Una vez que vea la salida de sqlmigrate para una migración un poco más compleja, puede apreciar que no tiene que crear todo este SQL a mano.




Cómo detecta Django los cambios en tus modelos

Ha visto cómo se ve un archivo de migración y cómo su lista de Operation clases define los cambios realizados en la base de datos. Pero, ¿cómo sabe exactamente Django qué operaciones deben incluirse en un archivo de migración? Podría esperar que Django compare sus modelos con el esquema de su base de datos, pero ese no es el caso.

Al ejecutar makemigrations , Django no inspeccionar su base de datos. Tampoco compara el archivo de su modelo con una versión anterior. En su lugar, Django pasa por todas las migraciones que se han aplicado y crea un estado de proyecto de cómo deberían verse los modelos. Este estado del proyecto se compara luego con las definiciones de su modelo actual y se crea una lista de operaciones que, cuando se aplican, actualizarían el estado del proyecto con las definiciones del modelo.


Jugar al ajedrez con Django

Puedes pensar en tus modelos como un tablero de ajedrez, y Django es un gran maestro de ajedrez que te ve jugar contra ti mismo. Pero el gran maestro no observa cada uno de tus movimientos. El gran maestro solo mira el tablero cuando gritas makemigrations .

Debido a que solo hay un conjunto limitado de movimientos posibles (y el gran maestro es un gran maestro), puede pensar en los movimientos que han sucedido desde la última vez que miró el tablero. Ella toma algunas notas y te deja jugar hasta que gritas makemigrations de nuevo.

Al mirar el tablero la próxima vez, el gran maestro no recuerda cómo se veía el tablero de ajedrez la última vez, pero puede revisar sus notas de los movimientos anteriores y construir un modelo mental de cómo se veía el tablero de ajedrez.

Ahora, cuando grites migrate , el gran maestro reproducirá todos los movimientos registrados en otro tablero de ajedrez y anotará en una hoja de cálculo cuáles de sus registros ya se han aplicado. Este segundo tablero de ajedrez es su base de datos, y la hoja de cálculo es django_migrations mesa.

Esta analogía es bastante adecuada, porque ilustra muy bien algunos comportamientos de las migraciones de Django:

  • Las migraciones de Django intentan ser eficientes: Al igual que el gran maestro asume que realizó la menor cantidad de movimientos, Django intentará crear las migraciones más eficientes. Si agrega un campo llamado A a un modelo, luego cámbiele el nombre a B y luego ejecute makemigrations , Django creará una nueva migración para agregar un campo llamado B .

  • Las migraciones de Django tienen sus límites: Si realiza muchos movimientos antes de dejar que el gran maestro mire el tablero de ajedrez, es posible que no pueda volver a trazar los movimientos exactos de cada pieza. Del mismo modo, es posible que Django no realice la migración correcta si realiza demasiados cambios a la vez.

  • La migración de Django espera que sigas las reglas: Cuando hace algo inesperado, como tomar una pieza al azar del tablero o jugar con las notas, es posible que el gran maestro no se dé cuenta al principio, pero tarde o temprano, se dará por vencido y se negará a continuar. Lo mismo sucede cuando te metes con django_migrations o cambie el esquema de su base de datos fuera de las migraciones, por ejemplo, eliminando la tabla de la base de datos para un modelo.



Comprender SeparateDatabaseAndState

Ahora que conoce el estado del proyecto que construye Django, es hora de echar un vistazo más de cerca a la operación SeparateDatabaseAndState . Esta operación puede hacer exactamente lo que su nombre implica:puede separar el estado del proyecto (el modelo mental que construye Django) de su base de datos.

SeparateDatabaseAndState se instancia con dos listas de operaciones:

  1. state_operations contiene operaciones que solo se aplican al estado del proyecto.
  2. database_operations contiene operaciones que solo se aplican a la base de datos.

Esta operación le permite realizar cualquier tipo de cambio en su base de datos, pero es su responsabilidad asegurarse de que el estado del proyecto se ajuste a la base de datos después. Ejemplos de casos de uso para SeparateDatabaseAndState están moviendo un modelo de una aplicación a otra o creando un índice en una base de datos enorme sin tiempo de inactividad.

SeparateDatabaseAndState es una operación avanzada y no necesitará trabajar con migraciones en su primer día y tal vez nunca. SeparateDatabaseAndState es similar a la cirugía del corazón. Conlleva bastante riesgo y no es algo que se hace solo por diversión, pero a veces es un procedimiento necesario para mantener vivo al paciente.




Conclusión

Esto concluye su inmersión profunda en las migraciones de Django. ¡Felicidades! Ha cubierto una gran cantidad de temas avanzados y ahora tiene una sólida comprensión de lo que sucede bajo el capó de las migraciones.

Aprendiste que:

  • Django realiza un seguimiento de las migraciones aplicadas en la tabla de migraciones de Django.
  • Las migraciones de Django consisten en archivos simples de Python que contienen una Migration clase.
  • Django sabe qué cambios realizar desde las operations lista en Migration clases.
  • Django compara tus modelos con un estado de proyecto que construye a partir de las migraciones.

Con este conocimiento, ahora está listo para abordar la tercera parte de la serie sobre migraciones de Django, donde aprenderá a usar las migraciones de datos para realizar cambios únicos en sus datos de manera segura. ¡Estén atentos!

Este artículo usó el bitcoin_tracker Proyecto Django construido en Django Migrations:A Primer. Puede volver a crear ese proyecto trabajando en ese artículo o puede descargar el código fuente:

Descargar código fuente: Haga clic aquí para descargar el código del proyecto de migraciones de Django que usará en este artículo.