Titta nu Denna handledning har en relaterad videokurs skapad av Real Python-teamet. Se den tillsammans med den skriftliga handledningen för att fördjupa din förståelse:Django Migrations 101
Sedan version 1.7 har Django kommit med inbyggt stöd för databasmigrering. I Django går databasmigreringar vanligtvis hand i hand med modeller:när du kodar en ny modell genererar du också en migrering för att skapa den nödvändiga tabellen i databasen. Men migrering kan göra mycket mer.
Du kommer att lära dig hur Django Migrations fungerar och hur du kan få ut det mesta av dem under loppet av fyra artiklar och en video:
- Del 1:Django Migrations:A Primer (nuvarande artikel)
- Del 2:Gräva djupare i migrationer
- Del 3:Datamigreringar
- Video:Django 1.7 Migrations - primer
I den här artikeln kommer du att bli bekväm med Django-migreringar och lära dig följande:
- Hur man skapar databastabeller utan att skriva någon SQL
- Hur du ändrar din databas automatiskt efter att du har ändrat dina modeller
- Hur man återställer ändringar som gjorts i din databas
Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.
Problemen som migrationer löser
Om du är ny på Django eller webbutveckling i allmänhet är du kanske inte bekant med konceptet med databasmigreringar, och det kanske inte verkar självklart varför de är en bra idé.
Först, låt oss snabbt definiera ett par termer för att se till att alla är på samma sida. Django är designad för att fungera med en relationsdatabas, lagrad i ett relationsdatabashanteringssystem som PostgreSQL, MySQL eller SQLite.
I en relationsdatabas är data organiserade i tabeller. En databastabell har ett visst antal kolumner, men den kan ha hur många rader som helst. Varje kolumn har en specifik datatyp, som en sträng med en viss maximal längd eller ett positivt heltal. Beskrivningen av alla tabeller med deras kolumner och deras respektive datatyper kallas ett databasschema.
Alla databassystem som stöds av Django använder språket SQL för att skapa, läsa, uppdatera och radera data i en relationsdatabas. SQL används också för att skapa, ändra och ta bort själva databastabellerna.
Att arbeta direkt med SQL kan vara ganska besvärligt, så för att göra ditt liv enklare kommer Django med en objektrelationell kartläggare, eller ORM för kort. ORM mappar relationsdatabasen till en värld av objektorienterad programmering. Istället för att definiera databastabeller i SQL skriver du Django-modeller i Python. Dina modeller definierar databasfält, som motsvarar kolumnerna i deras databastabeller.
Här är ett exempel på hur en Django-modellklass mappas till en databastabell:
Men bara att definiera en modellklass i en Python-fil får inte en databastabell magiskt att dyka upp från ingenstans. Att skapa databastabellerna för att lagra dina Django-modeller är uppgiften med en databasmigrering. Dessutom, när du gör en ändring i dina modeller, som att lägga till ett fält, måste databasen också ändras. Migrationer hanterar det också.
Här är några sätt Django-migreringar gör ditt liv enklare.
Göra databasändringar utan SQL
Utan migrering skulle du behöva ansluta till din databas och skriva in ett gäng SQL-kommandon eller använda ett grafiskt verktyg som PHPMyAdmin för att ändra databasschemat varje gång du vill ändra din modelldefinition.
I Django skrivs migrering främst i Python, så du behöver inte kunna någon SQL om du inte har riktigt avancerade användningsfall.
Undvika upprepning
Att skapa en modell och sedan skriva SQL för att skapa databastabellerna för den skulle vara repetitivt.
Migrering genereras från dina modeller, se till att du inte upprepar dig själv.
Säkerställa modelldefinitioner och databasschemat synkroniserade
Vanligtvis har du flera instanser av din databas, till exempel en databas för varje utvecklare i ditt team, en databas för testning och en databas med livedata.
Utan migrering måste du utföra alla schemaändringar på var och en av dina databaser, och du måste hålla reda på vilka ändringar som redan har gjorts i vilken databas.
Med Django Migrations kan du enkelt hålla flera databaser synkroniserade med dina modeller.
Spårningsdatabasschemaändring i versionskontroll
Ett versionskontrollsystem, som Git, är utmärkt för kod, men inte så mycket för databasscheman.
Eftersom migreringarna är vanliga Python i Django kan du lägga dem i ett versionskontrollsystem precis som vilken annan kod som helst.
Vid det här laget är du förhoppningsvis övertygad om att migrering är ett användbart och kraftfullt verktyg. Låt oss börja lära oss hur man släpper lös den kraften.
Konfigurera ett Django-projekt
Genom den här handledningen kommer du att arbeta med en enkel Bitcoin-spårningsapp som ett exempelprojekt.
Det första steget är att installera Django. Så här gör du det på Linux eller macOS X med en virtuell miljö:
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install "Django==2.1.*"
...
Successfully installed Django-2.1.3
Nu har du skapat en ny virtuell miljö och aktiverat den, samt installerat Django i den virtuella miljön.
Observera att på Windows skulle du köra env/bin/activate.bat
istället för source env/bin/activate
för att aktivera din virtuella miljö.
För enklare läsbarhet inkluderar konsolexemplen inte (env)
del av uppmaningen från och med nu.
Med Django installerat kan du skapa projektet med följande kommandon:
$ django-admin.py startproject bitcoin_tracker
$ cd bitcoin_tracker
$ python manage.py startapp historical_data
Detta ger dig ett enkelt projekt och en app som heter historical_data
. Du bör nu ha denna katalogstruktur:
bitcoin_tracker/
|
├── bitcoin_tracker/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
|
├── historical_data/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
| |
│ ├── models.py
│ ├── tests.py
│ └── views.py
|
└── manage.py
Inom bitcoin_tracker
katalogen, det finns två underkataloger:bitcoin_tracker
för projektomfattande filer och historical_data
som innehåller filer för appen du skapade.
Nu, för att skapa en modell, lägg till den här klassen i historical_data/models.py
:
class PriceHistory(models.Model):
date = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
volume = models.PositiveIntegerField()
Detta är den grundläggande modellen för att hålla reda på Bitcoin-priser.
Glöm inte heller att lägga till den nyskapade appen i settings.INSTALLED_APPS
. Öppna bitcoin_tracker/settings.py
och lägg till historical_data
till listan INSTALLED_APPS
, så här:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'historical_data',
]
De andra inställningarna är bra för det här projektet. Denna handledning förutsätter att ditt projekt är konfigurerat för att använda en SQLite-databas, vilket är standard.
Skapa migrering
Med modellen skapad är det första du behöver göra att skapa en migrering för den. Du kan göra detta med följande kommando:
$ python manage.py makemigrations historical_data
Migrations for 'historical_data':
historical_data/migrations/0001_initial.py
- Create model PriceHistory
Obs! Ange namnet på programmet, historical_data
, är valfritt. Om du inte gör det skapas migrering för alla appar.
Detta skapar migreringsfilen som instruerar Django om hur man skapar databastabellerna för de modeller som definieras i din applikation. Låt oss ta en ny titt på katalogträdet:
bitcoin_tracker/
|
├── bitcoin_tracker/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
|
├── historical_data/
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
| |
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
|
├── db.sqlite3
└── manage.py
Som du kan se är migrations
katalogen innehåller nu en ny fil:0001_initial.py
.
Obs! Du kanske märker att du kör makemigrations
kommandot skapade också filen db.sqlite3
, som innehåller din SQLite-databas.
När du försöker komma åt en icke-existerande SQLite3-databasfil, skapas den automatiskt.
Detta beteende är unikt för SQLite3. Om du använder någon annan databasbackend som PostgreSQL eller MySQL, måste du skapa databasen själv innan kör makemigrations
.
Du kan ta en titt på databasen med dbshell
ledningskommando. I SQLite är kommandot för att lista alla tabeller helt enkelt .tables
:
$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
sqlite>
Databasen är fortfarande tom. Det kommer att ändras när du tillämpar migreringen. Skriv .quit
för att avsluta SQLite-skalet.
Tillämpa migrering
Du har nu skapat migreringen, men för att faktiskt göra några ändringar i databasen måste du tillämpa den med hanteringskommandot migrate
:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying historical_data.0001_initial... OK
Applying sessions.0001_initial... OK
Det händer mycket här! Enligt utdata har din migrering tillämpats framgångsrikt. Men var kommer alla andra migrationer ifrån?
Kom ihåg inställningen INSTALLED_APPS
? Några av de andra apparna som listas där kommer också med migrering, och migrate
managementkommandot tillämpar migreringarna för alla installerade appar som standard.
Ta en titt på databasen igen:
$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
auth_group django_admin_log
auth_group_permissions django_content_type
auth_permission django_migrations
auth_user django_session
auth_user_groups historical_data_pricehistory
auth_user_user_permissions
sqlite>
Nu finns det flera bord. Deras namn ger dig en uppfattning om deras syfte. Migreringen som du genererade i föregående steg har skapat historical_data_pricehistory
tabell. Låt oss inspektera det med .schema
kommando:
sqlite> .schema --indent historical_data_pricehistory
CREATE TABLE IF NOT EXISTS "historical_data_pricehistory"(
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" datetime NOT NULL,
"price" decimal NOT NULL,
"volume" integer unsigned NOT NULL
);
.schema
kommandot skriver ut CREATE
uttalande som du skulle köra för att skapa tabellen. Parametern --indent
formaterar den snyggt. Även om du inte är bekant med SQL-syntax kan du se att schemat för historical_data_pricehistory
tabellen återspeglar fälten i PriceHistory
modell.
Det finns en kolumn för varje fält och en extra kolumn id
för primärnyckeln, som Django skapar automatiskt om du inte uttryckligen anger en primärnyckel i din modell.
Det här är vad som händer om du kör migrate
kommandot igen:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Ingenting! Django kommer ihåg vilka migreringar som redan har tillämpats och försöker inte köra dem igen.
Det är värt att notera att du också kan begränsa migrate
hanteringskommando till en enskild app:
$ python manage.py migrate historical_data
Operations to perform:
Apply all migrations: historical_data
Running migrations:
No migrations to apply.
Som du kan se tillämpar Django nu bara migrering för historical_data
app.
När du kör migreringarna för första gången är det en bra idé att tillämpa alla migreringarna för att säkerställa att din databas innehåller de nödvändiga tabellerna för de funktioner du kan ta för givna, som användarautentisering och sessioner.
Ändra modell
Dina modeller är inte huggna i sten. Dina modeller kommer att förändras när ditt Django-projekt får fler funktioner. Du kan lägga till eller ta bort fält eller ändra deras typer och alternativ.
När du ändrar definitionen av en modell måste databastabellerna som används för att lagra dessa modeller också ändras. Om dina modelldefinitioner inte stämmer överens med ditt nuvarande databasschema kommer du troligen att stöta på en django.db.utils.OperationalError
.
Så hur ändrar du databastabellerna? Genom att skapa och tillämpa en migrering.
När du testar din Bitcoin-spårare inser du att du gjorde ett misstag. Människor säljer bråkdelar av en Bitcoin, så fältet volume
bör vara av typen DecimalField
istället för PositiveIntegerField
.
Låt oss ändra modellen så att den ser ut så här:
class PriceHistory(models.Model):
date = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
volume = models.DecimalField(max_digits=7, decimal_places=3)
Utan migreringar skulle du behöva ta reda på SQL-syntaxen för att göra ett PositiveIntegerField
till ett DecimalField
. Lyckligtvis kommer Django att hantera det åt dig. Säg bara till att den ska göra migrering:
$ python manage.py makemigrations
Migrations for 'historical_data':
historical_data/migrations/0002_auto_20181112_1950.py
- Alter field volume on pricehistory
Obs! Namnet på migreringsfilen (0002_auto_20181112_1950.py
) baseras på den aktuella tiden och kommer att vara annorlunda om du följer med på ditt system.
Nu tillämpar du denna migrering på din databas:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
Applying historical_data.0002_auto_20181112_1950... OK
Migreringen har tillämpats framgångsrikt, så du kan använda dbshell
för att verifiera att ändringarna hade effekt:
$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .schema --indent historical_data_pricehistory
CREATE TABLE IF NOT EXISTS "historical_data_pricehistory" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" datetime NOT NULL,
"price" decimal NOT NULL,
"volume" decimal NOT NULL
);
Om du jämför det nya schemat med schemat du såg tidigare kommer du att märka att typen av volume
kolumnen har ändrats från integer
till decimal
för att återspegla förändringen av volume
fältet i modellen från PositiveIntegerField
till DecimalField
.
Lista migrering
Om du vill veta vilka migrationer som finns i ett Django-projekt behöver du inte gräva igenom migrations
kataloger över dina installerade appar. Du kan använda showmigrations
kommando:
$ ./manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
historical_data
[X] 0001_initial
[X] 0002_auto_20181112_1950
sessions
[X] 0001_initial
Detta listar alla appar i projektet och migreringarna som är kopplade till varje app. Dessutom kommer det att sätta ett stort X
bredvid migreringarna som redan har tillämpats.
För vårt lilla exempel, showmigrations
kommandot är inte speciellt spännande, men det kommer väl till pass när du börjar arbeta på en befintlig kodbas eller arbetar i ett team där du inte är den enda som lägger till migrering.
Avapplying Migrations
Nu vet du hur du gör ändringar i ditt databasschema genom att skapa och tillämpa migrering. Vid någon tidpunkt kanske du vill ångra ändringar och byta tillbaka till ett tidigare databasschema eftersom du:
- Vill du testa en migrering som en kollega skrev
- Inse att en förändring du gjorde var en dålig idé
- Arbeta med flera funktioner med olika databasändringar parallellt
- Vill du återställa en säkerhetskopia som skapades när databasen fortfarande hade ett äldre schema
Lyckligtvis behöver migrationer inte vara en enkelriktad gata. I många fall kan effekterna av en migrering ångras genom att avaktivera en migrering. För att avaktivera en migrering måste du anropa migrate
med namnet på appen och namnet på migreringen före migreringen som du vill ta bort.
Om du vill återställa migreringen 0002_auto_20181112_1950
i din historical_data
app måste du skicka 0001_initial
som ett argument till migrate
kommando:
$ python manage.py migrate historical_data 0001_initial
Operations to perform:
Target specific migration: 0001_initial, from historical_data
Running migrations:
Rendering model states... DONE
Unapplying historical_data.0002_auto_20181112_1950... OK
Migreringen har inte tillämpats, vilket innebär att ändringarna i databasen har återställts.
Att ta bort en migrering tar inte bort dess migreringsfil. Nästa gång du kör migrate
kommandot kommer migreringen att tillämpas igen.
Varning: Blanda inte ihop icke-tillämpliga migreringar med ångra-åtgärden du är van vid från din favorittextredigerare.
Inte alla databasoperationer kan återställas helt. Om du tar bort ett fält från en modell, skapar en migrering och tillämpar den, kommer Django att ta bort respektive kolumn från databasen.
Om du tar bort den migreringen kommer kolumnen att återskapas, men den data som lagrades i den kolumnen kommer inte tillbaka!
När du har att göra med migreringsnamn, sparar Django dig några knapptryckningar genom att inte tvinga dig att stava hela namnet på migreringen. Den behöver precis tillräckligt med namn för att identifiera den unikt.
I det föregående exemplet hade det räckt med att köra python manage.py migrate historical_data 0001
.
Namngivningsmigreringar
I exemplet ovan kom Django på ett namn för migreringen baserat på tidsstämpeln—något i stil med *0002_auto_20181112_1950
. Om du inte är nöjd med det kan du använda --name
parameter för att ange ett anpassat namn (utan .py
förlängning).
För att testa det måste du först ta bort den gamla migreringen. Du har redan tagit bort den, så du kan säkert ta bort filen:
$ rm historical_data/migrations/0002_auto_20181112_1950.py
Nu kan du återskapa den med ett mer beskrivande namn:
$ ./manage.py makemigrations historical_data --name switch_to_decimals
Detta kommer att skapa samma migrering som tidigare förutom med det nya namnet 0002_switch_to_decimals
.
Slutsats
Du täckte en hel del mark i den här handledningen och lärde dig grunderna i Django-migrering.
För att sammanfatta, de grundläggande stegen för att använda Django-migrering ser ut så här:
- Skapa eller uppdatera en modell
- Kör
./manage.py makemigrations <app_name>
- Kör
./manage.py migrate
för att migrera allt eller./manage.py migrate <app_name>
för att migrera en enskild app - Upprepa vid behov
Det är allt! Det här arbetsflödet kommer att fungera större delen av tiden, men om saker och ting inte fungerar som förväntat vet du också hur du listar och avaktiverar migrering.
Om du tidigare skapat och modifierat dina databastabeller med handskriven SQL har du nu blivit mycket effektivare genom att delegera detta arbete till Django-migreringar.
I nästa handledning i den här serien kommer du att gräva djupare i ämnet och lära dig hur Django Migrations fungerar under huven.
Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.
Skål!