Det här är den andra artikeln i vår Django-migreringsserie:
- Del 1:Django Migrations:A Primer
- Del 2:Gräva djupare in i Django-migrering (nuvarande artikel)
- Del 3:Datamigreringar
- Video:Django 1.7 Migrations - A Primer
I den tidigare artikeln i den här serien lärde du dig om syftet med Django-migrering. Du har blivit bekant med grundläggande användningsmönster som att skapa och tillämpa migrering. Nu är det dags att gräva djupare i migrationssystemet och ta en titt på några av dess underliggande mekanik.
I slutet av den här artikeln vet du:
- Hur Django håller reda på migrering
- Hur migrering vet vilka databasoperationer som ska utföras
- Hur definieras beroenden mellan migrering
När du har lindat huvudet runt den här delen av Django-migreringssystemet är du väl förberedd för att skapa dina egna anpassade migreringar. Låt oss hoppa in där vi slutade!
Den här artikeln använder bitcoin_tracker
Django-projekt byggt i Django Migrations:A Primer. Du kan antingen återskapa det projektet genom att gå igenom artikeln eller så kan du ladda ner källkoden:
Ladda ned källkod: Klicka här för att ladda ner koden för Django-migreringsprojektet som du kommer att använda i den här artikeln.
Hur Django vet vilka migreringar som ska tillämpas
Låt oss sammanfatta det allra sista steget i föregående artikel i serien. Du skapade en migrering och använde sedan alla tillgängliga migreringar med python manage.py migrate
.Om kommandot kördes framgångsrikt matchar dina databastabeller nu din modells definitioner.
Vad händer om du kör det kommandot igen? Låt oss prova det:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Inget hände! När en migrering väl har tillämpats på en databas kommer Django inte att tillämpa denna migrering på just den databasen igen. För att säkerställa att en migrering endast tillämpas en gång måste man hålla reda på de migrering som har tillämpats.
Django använder en databastabell som heter django_migrations
. Django skapar automatiskt den här tabellen i din databas första gången du tillämpar någon migrering. För varje migrering som tillämpas eller förfalskas, infogas en ny rad i tabellen.
Till exempel, så här ser den här tabellen ut i vår bitcoin_tracker
projekt:
ID | App | Namn | Tillämpat |
---|---|---|---|
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 |
Som du kan se finns det en post för varje tillämpad migrering. Tabellen innehåller inte bara migreringarna från vår historical_data
app, men även migreringarna från alla andra installerade appar.
Nästa gång migrering körs kommer Django att hoppa över migreringarna som anges i databastabellen. Detta innebär att även om du manuellt ändrar filen för en migrering som redan har tillämpats, kommer Django att ignorera dessa ändringar, så länge det redan finns en post för den i databasen.
Du kan lura Django att köra om en migrering genom att ta bort motsvarande rad från tabellen, men detta är sällan en bra idé och kan lämna dig med ett trasigt migreringssystem.
Migreringsfilen
Vad händer när du kör python manage.py makemigrations <appname>
? Django letar efter ändringar som gjorts i modellerna i din app <appname>
. Om den hittar någon, som en modell som har lagts till, skapar den en migreringsfil i migrations
underkatalog. Den här migreringsfilen innehåller en lista över operationer för att få ditt databasschema att synkronisera med din modelldefinition.
Obs! Din app måste vara listad i INSTALLED_APPS
inställning, och den måste innehålla en migrations
katalog med en __init__.py
fil. Annars kommer Django inte att skapa några migreringar för det.
migrations
katalogen skapas automatiskt när du skapar en ny app med startapp
managementkommando, men det är lätt att glömma när du skapar en app manuellt.
Migreringsfilerna är bara Python, så låt oss ta en titt på den första migreringsfilen i historical_prices
app. Du hittar den på historical_prices/migrations/0001_initial.py
. Det borde se ut ungefär så här:
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,),
),
]
Som du kan se innehåller den en enda klass som heter Migration
som ärver från django.db.migrations.Migration
. Det här är klassen som migreringsramverket letar efter och kör när du ber det att tillämpa migrering.
Migration
klass innehåller två huvudlistor:
dependencies
operations
Migreringsåtgärder
Låt oss titta på operations
lista först. Den här tabellen innehåller de operationer som ska utföras som en del av migreringen. Operationer är underklasser av klassen django.db.migrations.operations.base.Operation
. Här är de vanliga operationerna som är inbyggda i Django:
Operation Class | Beskrivning |
---|---|
CreateModel | Skapar en ny modell och motsvarande databastabell |
DeleteModel | Tar bort en modell och släpper dess databastabell |
RenameModel | Byter namn på en modell och byter namn på dess databastabell |
AlterModelTable | Byter namn på databastabellen för en modell |
AlterUniqueTogether | Ändrar de unika begränsningarna för en modell |
AlterIndexTogether | Ändrar indexen för en modell |
AlterOrderWithRespectTo | Skapar eller tar bort _order kolumn för en modell |
AlterModelOptions | Ändrar olika modellalternativ utan att påverka databasen |
AlterModelManagers | Ändrar tillgängliga hanterare under migrering |
AddField | Lägger till ett fält i en modell och motsvarande kolumn i databasen |
RemoveField | Tar bort ett fält från en modell och släpper motsvarande kolumn från databasen |
AlterField | Ändrar ett fälts definition och ändrar dess databaskolumn vid behov |
RenameField | Byter namn på ett fält och, om nödvändigt, även dess databaskolumn |
AddIndex | Skapar ett index i databastabellen för modellen |
RemoveIndex | Tar bort ett index från databastabellen för modellen |
Notera hur operationerna är namngivna efter ändringar som gjorts i modelldefinitioner, inte de åtgärder som utförs på databasen. När du tillämpar en migrering är varje operation ansvarig för att generera de nödvändiga SQL-satserna för din specifika databas. Till exempel CreateModel
skulle generera en CREATE TABLE
SQL-sats.
Utanför lådan har migreringarna stöd för alla standarddatabaser som Django stöder. Så om du håller dig till operationerna som listas här, så kan du göra mer eller mindre vilka ändringar du vill på dina modeller utan att behöva oroa dig för den underliggande SQL. Det är allt gjort för dig.
Obs! I vissa fall kanske Django inte upptäcker dina ändringar korrekt. Om du byter namn på en modell och ändrar flera av dess fält kan Django förväxla detta med en ny modell.
Istället för en RenameModel
och flera AlterField
operationer kommer den att skapa en DeleteModel
och en CreateModel
drift. Istället för att byta namn på databastabellen för modellen kommer den att släppa den och skapa en ny tabell med det nya namnet, vilket i praktiken tar bort all din data!
Gör det till en vana att kontrollera de genererade migreringarna och testa dem på en kopia av din databas innan du kör dem på produktionsdata.
Django tillhandahåller ytterligare tre operationsklasser för avancerade användningsfall:
RunSQL
låter dig köra anpassad SQL i databasen.RunPython
låter dig köra vilken Python-kod som helst.SeparateDatabaseAndState
är en specialiserad operation för avancerad användning.
Med dessa operationer kan du i princip göra vilka ändringar du vill i din databas. Du hittar dock inte dessa operationer i en migrering som har skapats automatiskt med makemigrations
ledningskommando.
Sedan Django 2.0 finns det även ett par PostgreSQL-specifika operationer tillgängliga i django.contrib.postgres.operations
som du kan använda för att installera olika PostgreSQL-tillägg:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Observera att en migrering som innehåller en av dessa operationer kräver en databasanvändare med superanvändarbehörighet.
Sist men inte minst kan du också skapa dina egna operationsklasser. Om du vill undersöka det, ta en titt på Django-dokumentationen om hur du skapar anpassade migreringsoperationer.
Migreringsberoenden
dependencies
listan i en migreringsklass innehåller alla migreringar som måste tillämpas innan denna migrering kan tillämpas.
I 0001_initial.py
migrering som du såg ovan, inget måste tillämpas tidigare så det finns inga beroenden. Låt oss ta en titt på den andra migreringen i historical_prices
app. I filen 0002_switch_to_decimals.py
, dependencies
attribut för Migration
har en post:
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),
),
]
Beroendet ovan säger att migration 0001_initial
av appen historical_data
måste köras först. Det är vettigt eftersom migreringen 0001_initial
skapar tabellen som innehåller fältet som migreringen 0002_switch_to_decimals
vill ändra.
En migrering kan också vara beroende av en migrering från en annan app, så här:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Detta är vanligtvis nödvändigt om en modell har en främmande nyckel som pekar på en modell i en annan app.
Alternativt kan du också tvinga fram att en migrering körs före en annan migrering med attributet run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Beroenden kan också kombineras så att du kan ha flera beroenden. Den här funktionen ger mycket flexibilitet, eftersom du kan ta emot främmande nycklar som är beroende av modeller från olika appar.
Alternativet att uttryckligen definiera beroenden mellan migreringarna innebär också att numreringen av migreringarna (vanligtvis 0001
, 0002
, 0003
, …) representerar inte strikt den ordning i vilken migreringarna tillämpas. Du kan lägga till vilket beroende du vill och på så sätt styra ordningen utan att behöva numrera om alla migreringarna.
Visa migreringen
Du behöver i allmänhet inte oroa dig för den SQL som migrering genererar. Men om du vill dubbelkolla att den genererade SQL-koden är meningsfull eller bara är nyfiken på hur den ser ut, då har Django dig täckt med sqlmigrate
hanteringskommando:
$ 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;
Om du gör det listar du de underliggande SQL-frågorna som kommer att genereras av den angivna migreringen, baserat på databasen i din settings.py
fil. När du skickar parametern --backwards
, Django genererar SQL för att avaktivera migreringen:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
När du ser utdata från sqlmigrate
för en lite mer komplex migrering kanske du inser att du inte behöver skapa all denna SQL för hand!
Hur Django upptäcker ändringar i dina modeller
Du har sett hur en migreringsfil ser ut och hur dess lista över Operation
klasser definierar ändringarna som utförs i databasen. Men exakt hur vet Django vilka operationer som ska gå in i en migreringsfil? Du kan förvänta dig att Django jämför dina modeller med ditt databasschema, men så är inte fallet.
När du kör makemigrations
, Django gör inte inspektera din databas. Den jämför inte heller din modellfil med en tidigare version. Istället går Django igenom alla migrationer som har tillämpats och bygger en projektstatus över hur modellerna ska se ut. Detta projekttillstånd jämförs sedan med dina nuvarande modelldefinitioner, och en lista över operationer skapas, som, när den tillämpas, skulle uppdatera projekttillståndet med modelldefinitionerna.
Spela schack med Django
Du kan tänka på dina modeller som ett schackbräde, och Django är en schackstormästare som ser dig spela mot dig själv. Men stormästaren tittar inte på alla dina rörelser. Stormästaren tittar bara på tavlan när du ropar makemigrations
.
Eftersom det bara finns en begränsad uppsättning möjliga drag (och stormästaren är en stormästare), kan hon komma på de drag som har hänt sedan hon senast tittade på tavlan. Hon tar några anteckningar och låter dig spela tills du ropar makemigrations
igen.
När man tittar på tavlan nästa gång kommer stormästaren inte ihåg hur schackbrädet såg ut senast, men hon kan gå igenom sina anteckningar om de tidigare dragen och bygga en mental modell av hur schackbrädet såg ut.
Nu, när du ropar migrate
, kommer stormästaren att spela om alla inspelade drag på ett annat schackbräde och anteckna i ett kalkylblad vilka av hennes rekord som redan har tillämpats. Detta andra schackbräde är din databas, och kalkylarket är django_migrations
bord.
Denna analogi är ganska passande, eftersom den på ett bra sätt illustrerar vissa beteenden hos Django-migrering:
-
Django-migreringar försöker vara effektiva: Precis som stormästaren antar att du gjort minst antal drag, kommer Django att försöka skapa de mest effektiva migreringarna. Om du lägger till ett fält med namnet
A
till en modell, döp sedan om den tillB
, och kör sedanmakemigrations
, då kommer Django att skapa en ny migrering för att lägga till ett fält med namnetB
. -
Django-migreringar har sina begränsningar: Om du gör många drag innan du låter stormästaren titta på schackbrädet, kanske hon inte kan spåra de exakta rörelserna för varje pjäs. På samma sätt kanske Django inte kommer på rätt migrering om du gör för många ändringar samtidigt.
-
Django-migrering förväntar sig att du spelar enligt reglerna: När du gör något oväntat, som att ta en slumpmässig pjäs från tavlan eller bråka med noterna, kanske stormästaren inte märker det först, men förr eller senare kommer hon att kasta upp händerna och vägra fortsätta. Samma sak händer när du bråkar med
django_migrations
tabell eller ändra ditt databasschema utanför migrering, till exempel genom att ta bort databastabellen för en modell.
Förstå SeparateDatabaseAndState
Nu när du känner till projekttillståndet som Django bygger, är det dags att titta närmare på operationen SeparateDatabaseAndState
. Denna operation kan göra precis vad namnet antyder:den kan separera projekttillståndet (den mentala modellen som Django bygger) från din databas.
SeparateDatabaseAndState
instansieras med två listor med operationer:
state_operations
innehåller operationer som endast tillämpas på projekttillståndet.database_operations
innehåller operationer som endast tillämpas på databasen.
Den här operationen låter dig göra någon form av förändring av din databas, men det är ditt ansvar att se till att projektstatusen passar databasen efteråt. Exempel på användningsfall för SeparateDatabaseAndState
flyttar en modell från en app till en annan eller skapar ett index på en enorm databas utan driftstopp.
SeparateDatabaseAndState
är en avancerad operation och du behöver inte arbeta med migrering första dagen och kanske aldrig alls. SeparateDatabaseAndState
liknar hjärtkirurgi. Det innebär en hel del risker och är inte något man gör bara för skojs skull, men ibland är det en nödvändig procedur för att hålla patienten vid liv.
Slutsats
Detta avslutar din djupdykning i Django-migrationer. Grattis! Du har täckt en hel del avancerade ämnen och har nu en gedigen förståelse för vad som händer under migrationshuven.
Du lärde dig att:
- Django håller reda på tillämpade migreringar i Django-migreringstabellen.
- Django-migreringar består av vanliga Python-filer som innehåller en
Migration
klass. - Django vet vilka ändringar som ska utföras från
operations
lista iMigration
klasser. - Django jämför dina modeller med ett projekttillstånd som det bygger från migreringarna.
Med denna kunskap är du nu redo att ta itu med den tredje delen av serien om Django-migreringar, där du lär dig hur du använder datamigreringar för att säkert göra engångsändringar av din data. Håll utkik!
Den här artikeln använde bitcoin_tracker
Django-projekt byggt i Django Migrations:A Primer. Du kan antingen återskapa det projektet genom att gå igenom artikeln eller så kan du ladda ner källkoden:
Ladda ned källkod: Klicka här för att ladda ner koden för Django-migreringsprojektet som du kommer att använda i den här artikeln.