Questo è il secondo articolo della nostra serie sulle migrazioni di Django:
- Parte 1:Migrazioni Django:una guida introduttiva
- Parte 2:Approfondire le migrazioni di Django (articolo attuale)
- Parte 3:Migrazioni dei dati
- Video:Django 1.7 Migrations - Primer
Nell'articolo precedente di questa serie, hai appreso lo scopo delle migrazioni di Django. Hai acquisito familiarità con i modelli di utilizzo fondamentali come la creazione e l'applicazione di migrazioni. Ora è il momento di approfondire il sistema di migrazione e dare un'occhiata ad alcuni dei suoi meccanismi sottostanti.
Entro la fine di questo articolo, saprai:
- Come Django tiene traccia delle migrazioni
- Come le migrazioni sanno quali operazioni di database eseguire
- Come vengono definite le dipendenze tra le migrazioni
Una volta che hai avvolto la testa in questa parte del sistema di migrazione di Django, sarai ben preparato per creare le tue migrazioni personalizzate. Riprendiamo da dove ci eravamo interrotti!
Questo articolo utilizza il bitcoin_tracker
Progetto Django realizzato in Django Migrations:A Primer. Puoi ricreare quel progetto lavorando su quell'articolo oppure puoi scaricare il codice sorgente:
Scarica codice sorgente: Clicca qui per scaricare il codice per il progetto di migrazione Django che utilizzerai in questo articolo.
Come Django sa quali migrazioni applicare
Ricapitoliamo l'ultimo passaggio del precedente articolo della serie. Hai creato una migrazione e poi applicato tutte le migrazioni disponibili con python manage.py migrate
.Se il comando è stato eseguito correttamente, le tabelle del database ora corrispondono alle definizioni del modello.
Cosa succede se esegui di nuovo quel comando? Proviamolo:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Non è successo niente! Una volta che una migrazione è stata applicata a un database, Django non applicherà più questa migrazione a quel particolare database. Garantire che una migrazione venga applicata solo una volta richiede di tenere traccia delle migrazioni che sono state applicate.
Django usa una tabella di database chiamata django_migrations
. Django crea automaticamente questa tabella nel tuo database la prima volta che applichi le migrazioni. Per ogni migrazione applicata o contraffatta, viene inserita una nuova riga nella tabella.
Ad esempio, ecco come appare questa tabella nel nostro bitcoin_tracker
progetto:
ID | App | Nome | Applicato |
---|---|---|---|
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 |
Come puoi vedere, c'è una voce per ogni migrazione applicata. La tabella non contiene solo le migrazioni dai nostri historical_data
app, ma anche le migrazioni da tutte le altre app installate.
Alla successiva esecuzione delle migrazioni, Django salterà le migrazioni elencate nella tabella del database. Ciò significa che, anche se modifichi manualmente il file di una migrazione già applicata, Django ignorerà queste modifiche, purché sia già presente una voce nel database.
Potresti indurre Django a rieseguire una migrazione eliminando la riga corrispondente dalla tabella, ma questa è raramente una buona idea e può lasciarti con un sistema di migrazione non funzionante.
Il file di migrazione
Cosa succede quando esegui python manage.py makemigrations <appname>
? Django cerca le modifiche apportate ai modelli nella tua app <appname>
. Se ne trova uno, come un modello che è stato aggiunto, crea un file di migrazione in migrations
sottodirectory. Questo file di migrazione contiene un elenco di operazioni per sincronizzare lo schema del database con la definizione del modello.
Nota: La tua app deve essere elencata in INSTALLED_APPS
e deve contenere un migrations
directory con un __init__.py
file. Altrimenti Django non creerà alcuna migrazione per esso.
Le migrations
la directory viene creata automaticamente quando crei una nuova app con startapp
comando di gestione, ma è facile dimenticarlo quando si crea un'app manualmente.
I file di migrazione sono solo Python, quindi diamo un'occhiata al primo file di migrazione nel historical_prices
app. Puoi trovarlo su historical_prices/migrations/0001_initial.py
. Dovrebbe assomigliare a questo:
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,),
),
]
Come puoi vedere, contiene una singola classe chiamata Migration
che eredita da django.db.migrations.Migration
. Questa è la classe che il framework di migrazione cercherà ed eseguirà quando gli chiederai di applicare le migrazioni.
La Migration
class contiene due elenchi principali:
dependencies
operations
Operazioni di migrazione
Diamo un'occhiata alle operations
elenca prima. Questa tabella contiene le operazioni da eseguire nell'ambito della migrazione. Le operazioni sono sottoclassi della classe django.db.migrations.operations.base.Operation
. Ecco le operazioni comuni integrate in Django:
Corso operativo | Descrizione |
---|---|
CreateModel | Crea un nuovo modello e la tabella del database corrispondente |
DeleteModel | Elimina un modello e ne elimina la tabella del database |
RenameModel | Rinomina un modello e rinomina la sua tabella del database |
AlterModelTable | Rinomina la tabella del database per un modello |
AlterUniqueTogether | Modifica i vincoli univoci di un modello |
AlterIndexTogether | Cambia gli indici di un modello |
AlterOrderWithRespectTo | Crea o elimina il _order colonna per un modello |
AlterModelOptions | Modifica varie opzioni del modello senza influire sul database |
AlterModelManagers | Modifica i gestori disponibili durante le migrazioni |
AddField | Aggiunge un campo a un modello e la colonna corrispondente nel database |
RemoveField | Rimuove un campo da un modello e rimuove la colonna corrispondente dal database |
AlterField | Modifica la definizione di un campo e, se necessario, ne altera la colonna del database |
RenameField | Rinomina un campo e, se necessario, anche la sua colonna del database |
AddIndex | Crea un indice nella tabella del database per il modello |
RemoveIndex | Rimuove un indice dalla tabella del database per il modello |
Nota come le operazioni prendono il nome dalle modifiche apportate alle definizioni del modello, non dalle azioni eseguite sul database. Quando si applica una migrazione, ogni operazione è responsabile della generazione delle istruzioni SQL necessarie per il database specifico. Ad esempio, CreateModel
genererebbe un CREATE TABLE
Istruzione SQL.
Per impostazione predefinita, le migrazioni supportano tutti i database standard supportati da Django. Quindi, se ti attieni alle operazioni elencate qui, puoi apportare più o meno tutte le modifiche ai tuoi modelli che desideri, senza doversi preoccupare dell'SQL sottostante. È tutto fatto per te.
Nota: In alcuni casi, Django potrebbe non rilevare correttamente le modifiche. Se rinomini un modello e modifichi molti dei suoi campi, Django potrebbe scambiarlo per un nuovo modello.
Invece di un RenameModel
e diversi AlterField
operazioni, creerà un DeleteModel
e un CreateModel
operazione. Invece di rinominare la tabella del database per il modello, la rilascerà e creerà una nuova tabella con il nuovo nome, eliminando di fatto tutti i tuoi dati!
Prendi l'abitudine di controllare le migrazioni generate e testarle su una copia del tuo database prima di eseguirle sui dati di produzione.
Django fornisce altre tre classi operative per casi d'uso avanzati:
RunSQL
ti consente di eseguire SQL personalizzato nel database.RunPython
ti consente di eseguire qualsiasi codice Python.SeparateDatabaseAndState
è un'operazione specializzata per usi avanzati.
Con queste operazioni, puoi praticamente apportare tutte le modifiche che desideri al tuo database. Tuttavia, non troverai queste operazioni in una migrazione che è stata creata automaticamente con makemigrations
comando di gestione.
Da Django 2.0, ci sono anche un paio di operazioni specifiche per PostgreSQL disponibili in django.contrib.postgres.operations
che puoi utilizzare per installare varie estensioni PostgreSQL:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Tieni presente che una migrazione contenente una di queste operazioni richiede un utente del database con privilegi di superutente.
Ultimo ma non meno importante, puoi anche creare le tue classi operative. Se vuoi approfondire, dai un'occhiata alla documentazione di Django sulla creazione di operazioni di migrazione personalizzate.
Dipendenze dalla migrazione
Le dependencies
list in una classe di migrazione contiene tutte le migrazioni che devono essere applicate prima che questa migrazione possa essere applicata.
Nel 0001_initial.py
migrazione che hai visto sopra, nulla deve essere applicato prima, quindi non ci sono dipendenze. Diamo un'occhiata alla seconda migrazione in historical_prices
app. Nel file 0002_switch_to_decimals.py
, le dependencies
attributo di Migration
ha una voce:
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 dipendenza sopra dice che la migrazione 0001_initial
dell'app historical_data
deve essere eseguito prima. Questo ha senso, perché la migrazione 0001_initial
crea la tabella contenente il campo che la migrazione 0002_switch_to_decimals
vuole cambiare.
Una migrazione può anche dipendere da una migrazione da un'altra app, come questa:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Di solito è necessario se un modello ha una chiave esterna che punta a un modello in un'altra app.
In alternativa, puoi anche imporre che una migrazione venga eseguita prima un'altra migrazione utilizzando l'attributo run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Le dipendenze possono anche essere combinate in modo da poter avere più dipendenze. Questa funzionalità offre molta flessibilità, poiché puoi ospitare chiavi esterne che dipendono da modelli di app diverse.
L'opzione per definire in modo esplicito le dipendenze tra le migrazioni significa anche che la numerazione delle migrazioni (solitamente 0001
, 0002
, 0003
, …) non rappresenta strettamente l'ordine in cui vengono applicate le migrazioni. Puoi aggiungere qualsiasi dipendenza tu voglia e quindi controllare l'ordine senza dover rinumerare tutte le migrazioni.
Visualizzazione della migrazione
In genere non devi preoccuparti dell'SQL generato dalle migrazioni. Ma se vuoi ricontrollare che l'SQL generato abbia senso o sei solo curioso di sapere come appare, allora Django ti copre con sqlmigrate
comando di gestione:
$ 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;
In questo modo verranno elencate le query SQL sottostanti che verranno generate dalla migrazione specificata, in base al database nel tuo settings.py
file. Quando passi il parametro --backwards
, Django genera l'SQL per annullare la migrazione:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Una volta visualizzato l'output di sqlmigrate
per una migrazione leggermente più complessa, potresti apprezzare di non dover creare tutto questo SQL a mano!
Come Django rileva le modifiche ai tuoi modelli
Hai visto come appare un file di migrazione e come si trova nell'elenco di Operation
classi definisce le modifiche apportate al database. Ma come fa esattamente Django a sapere quali operazioni dovrebbero essere inserite in un file di migrazione? Potresti aspettarti che Django confronti i tuoi modelli con lo schema del tuo database, ma non è così.
Durante l'esecuzione di makemigrations
, Django non ispeziona il tuo database. Né confronta il tuo file modello con una versione precedente. Invece, Django esamina tutte le migrazioni che sono state applicate e crea uno stato del progetto di come dovrebbero apparire i modelli. Questo stato del progetto viene quindi confrontato con le definizioni del modello corrente e viene creato un elenco di operazioni che, una volta applicate, aggiornerebbero lo stato del progetto con le definizioni del modello.
Giocare a scacchi con Django
Puoi pensare ai tuoi modelli come a una scacchiera e Django è un grande maestro di scacchi che ti guarda giocare contro te stesso. Ma il gran maestro non osserva ogni tua mossa. Il gran maestro guarda la lavagna solo quando gridi makemigrations
.
Poiché c'è solo una serie limitata di mosse possibili (e il gran maestro è un gran maestro), può inventare le mosse che sono accadute dall'ultima volta che ha guardato il tabellone. Prende alcune note e ti lascia giocare finché non gridi makemigrations
di nuovo.
Quando guarda la scacchiera la prossima volta, il Gran Maestro non ricorda che aspetto aveva la scacchiera l'ultima volta, ma può ripassare gli appunti delle mosse precedenti e costruire un modello mentale di come fosse la scacchiera.
Ora, quando gridi migrate
, il gran maestro riprodurrà tutte le mosse registrate su un'altra scacchiera e annoterà in un foglio di calcolo quali dei suoi record sono già stati applicati. Questa seconda scacchiera è il tuo database e il foglio di calcolo è django_migrations
tabella.
Questa analogia è abbastanza appropriata, perché illustra bene alcuni comportamenti delle migrazioni di Django:
-
Le migrazioni di Django cercano di essere efficienti: Proprio come il gran maestro presume che tu abbia fatto il minor numero di mosse, Django cercherà di creare le migrazioni più efficienti. Se aggiungi un campo denominato
A
a un modello, quindi rinominalo inB
, quindi eseguimakemigrations
, quindi Django creerà una nuova migrazione per aggiungere un campo denominatoB
. -
Le migrazioni di Django hanno i loro limiti: Se fai molte mosse prima di lasciare che il grande maestro guardi la scacchiera, allora potrebbe non essere in grado di ripercorrere i movimenti esatti di ogni pezzo. Allo stesso modo, Django potrebbe non fornire la migrazione corretta se apporti troppe modifiche contemporaneamente.
-
La migrazione di Django si aspetta che tu rispetti le regole: Quando fai qualcosa di inaspettato, come prendere un pezzo a caso dalla lavagna o pasticciare con gli appunti, il gran maestro potrebbe non accorgersene all'inizio, ma prima o poi alzerà le mani e si rifiuterà di continuare. Lo stesso accade quando si scherza con
django_migrations
tabella o modificare lo schema del database al di fuori delle migrazioni, ad esempio eliminando la tabella del database per un modello.
Capire SeparateDatabaseAndState
Ora che conosci lo stato del progetto creato da Django, è tempo di dare un'occhiata più da vicino all'operazione SeparateDatabaseAndState
. Questa operazione può fare esattamente ciò che suggerisce il nome:può separare lo stato del progetto (il modello mentale creato da Django) dal tuo database.
SeparateDatabaseAndState
viene istanziata con due elenchi di operazioni:
state_operations
contiene operazioni che vengono applicate solo allo stato del progetto.database_operations
contiene operazioni che vengono applicate solo al database.
Questa operazione ti consente di apportare qualsiasi tipo di modifica al tuo database, ma è tua responsabilità assicurarti che lo stato del progetto si adatti al database in seguito. Esempi di casi d'uso per SeparateDatabaseAndState
stanno spostando un modello da un'app all'altra o creando un indice su un database enorme senza tempi di inattività.
SeparateDatabaseAndState
è un'operazione avanzata e non avrai bisogno il tuo primo giorno di lavorare con le migrazioni e forse mai del tutto. SeparateDatabaseAndState
è simile alla chirurgia del cuore. Comporta un certo rischio e non è qualcosa che si fa solo per divertimento, ma a volte è una procedura necessaria per mantenere in vita il paziente.
Conclusione
Questo conclude la tua profonda immersione nelle migrazioni di Django. Congratulazioni! Hai trattato molti argomenti avanzati e ora hai una solida comprensione di ciò che accade sotto il cofano delle migrazioni.
Hai imparato che:
- Django tiene traccia delle migrazioni applicate nella tabella delle migrazioni di Django.
- Le migrazioni di Django consistono in semplici file Python contenenti una
Migration
classe. - Django sa quali modifiche eseguire dalle
operations
elenco inMigration
classi. - Django confronta i tuoi modelli con lo stato di un progetto che costruisce dalle migrazioni.
Con questa conoscenza, sei ora pronto per affrontare la terza parte della serie sulle migrazioni di Django, dove imparerai come utilizzare le migrazioni dei dati per apportare in sicurezza modifiche una tantum ai tuoi dati. Resta sintonizzato!
Questo articolo utilizzava il bitcoin_tracker
Progetto Django realizzato in Django Migrations:A Primer. Puoi ricreare quel progetto lavorando su quell'articolo oppure puoi scaricare il codice sorgente:
Scarica codice sorgente: Clicca qui per scaricare il codice per il progetto di migrazione Django che utilizzerai in questo articolo.