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.
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:
dependencies
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.
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:
RunSQL
le permite ejecutar SQL personalizado en la base de datos.RunPython
le permite ejecutar cualquier código de Python.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 aB
y luego ejecutemakemigrations
, Django creará una nueva migración para agregar un campo llamadoB
. -
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:
state_operations
contiene operaciones que solo se aplican al estado del proyecto.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 enMigration
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.