Database
 sql >> Database >  >> RDS >> Database

Scavare più a fondo nelle migrazioni di Django

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:

  1. dependencies
  2. 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:

  1. RunSQL ti consente di eseguire SQL personalizzato nel database.
  2. RunPython ti consente di eseguire qualsiasi codice Python.
  3. 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 in B , quindi esegui makemigrations , quindi Django creerà una nuova migrazione per aggiungere un campo denominato B .

  • 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:

  1. state_operations contiene operazioni che vengono applicate solo allo stato del progetto.
  2. 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 in Migration 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.