Se nu Denne vejledning har et relateret videokursus oprettet af Real Python-teamet. Se det sammen med det skriftlige selvstudie for at uddybe din forståelse:Django Migrations 101
Siden version 1.7 er Django kommet med indbygget understøttelse af databasemigrering. I Django går databasemigreringer normalt hånd i hånd med modeller:hver gang du koder en ny model, genererer du også en migrering for at skabe den nødvendige tabel i databasen. Migrationer kan dog meget mere.
Du kommer til at lære, hvordan Django Migrations fungerer, og hvordan du kan få mest muligt ud af dem i løbet af fire artikler og en video:
- Del 1:Django Migrations:A Primer (aktuel artikel)
- Del 2:Dybere ind i migrationer
- Del 3:Datamigrering
- Video:Django 1.7 Migrations - primer
I denne artikel bliver du fortrolig med Django-migreringer og lærer følgende:
- Sådan opretter du databasetabeller uden at skrive SQL
- Sådan ændrer du automatisk din database, efter du har ændret dine modeller
- Sådan gendannes ændringer foretaget i din database
Gratis bonus: Klik her for at få adgang til en gratis Django Learning Resources Guide (PDF), der viser dig tips og tricks samt almindelige faldgruber, du skal undgå, når du bygger Python + Django-webapplikationer.
De problemer, som migrationer løser
Hvis du er ny til Django eller webudvikling generelt, er du måske ikke bekendt med begrebet databasemigrering, og det virker måske ikke indlysende, hvorfor det er en god idé.
Lad os først hurtigt definere et par udtryk for at sikre, at alle er på samme side. Django er designet til at arbejde med en relationsdatabase, gemt i et relationsdatabasestyringssystem som PostgreSQL, MySQL eller SQLite.
I en relationsdatabase er data organiseret i tabeller. En databasetabel har et vist antal kolonner, men den kan have et hvilket som helst antal rækker. Hver kolonne har en bestemt datatype, såsom en streng med en bestemt maksimal længde eller et positivt heltal. Beskrivelsen af alle tabeller med deres kolonner og deres respektive datatyper kaldes et databaseskema.
Alle databasesystemer understøttet af Django bruger sproget SQL til at oprette, læse, opdatere og slette data i en relationsdatabase. SQL bruges også til at oprette, ændre og slette selve databasetabellerne.
At arbejde direkte med SQL kan være ret besværligt, så for at gøre dit liv lettere, kommer Django med en objektrelationel mapper, eller ORM for kort. ORM kortlægger den relationelle database til en verden af objektorienteret programmering. I stedet for at definere databasetabeller i SQL, skriver du Django-modeller i Python. Dine modeller definerer databasefelter, som svarer til kolonnerne i deres databasetabeller.
Her er et eksempel på, hvordan en Django-modelklasse er knyttet til en databasetabel:
Men bare det at definere en modelklasse i en Python-fil får ikke en databasetabel til at dukke op fra ingenting. At oprette databasetabellerne til at gemme dine Django-modeller er opgaven med en databasemigrering. Derudover skal databasen også ændres, når du foretager en ændring af dine modeller, f.eks. ved at tilføje et felt. Migrationer håndterer det også.
Her er et par måder, hvorpå Django-migreringer gør dit liv lettere.
Foretag databaseændringer uden SQL
Uden migreringer ville du skulle oprette forbindelse til din database og indtaste en masse SQL-kommandoer eller bruge et grafisk værktøj som PHPMyAdmin til at ændre databaseskemaet, hver gang du ville ændre din modeldefinition.
I Django er migreringer primært skrevet i Python, så du behøver ikke at kende nogen SQL, medmindre du har virkelig avancerede use cases.
Undgå gentagelse
At oprette en model og derefter skrive SQL for at oprette databasetabellerne til den ville være gentaget.
Migreringer genereres fra dine modeller, og sørg for, at du ikke gentager dig selv.
Sikring af modeldefinitioner og databaseskemaet synkroniseret
Normalt har du flere forekomster af din database, for eksempel en database for hver udvikler i dit team, en database til test og en database med live data.
Uden migreringer bliver du nødt til at udføre skemaændringer på hver enkelt af dine databaser, og du bliver nødt til at holde styr på, hvilke ændringer der allerede er foretaget i hvilken database.
Med Django Migrations kan du nemt holde flere databaser synkroniseret med dine modeller.
Sporing af databaseskemaændring i versionskontrol
Et versionskontrolsystem som Git er fremragende til kode, men ikke så meget til databaseskemaer.
Da migreringer er almindelige Python i Django, kan du placere dem i et versionskontrolsystem ligesom ethvert andet stykke kode.
Nu er du forhåbentlig overbevist om, at migrationer er et nyttigt og kraftfuldt værktøj. Lad os begynde at lære, hvordan man frigør den kraft.
Opsætning af et Django-projekt
Igennem denne tutorial kommer du til at arbejde på en simpel Bitcoin-tracker-app som et eksempelprojekt.
Det første trin er at installere Django. Sådan gør du det på Linux eller macOS X ved hjælp af et virtuelt miljø:
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install "Django==2.1.*"
...
Successfully installed Django-2.1.3
Nu har du oprettet et nyt virtuelt miljø og aktiveret det, samt installeret Django i det virtuelle miljø.
Bemærk, at på Windows vil du køre env/bin/activate.bat
i stedet for source env/bin/activate
for at aktivere dit virtuelle miljø.
For lettere læsbarhed vil konsoleksempler ikke inkludere (env)
del af prompten fra nu af.
Med Django installeret kan du oprette projektet ved hjælp af følgende kommandoer:
$ django-admin.py startproject bitcoin_tracker
$ cd bitcoin_tracker
$ python manage.py startapp historical_data
Dette giver dig et simpelt projekt og en app kaldet historical_data
. Du skulle nu have denne mappestruktur:
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
Inden for bitcoin_tracker
mappe, er der to undermapper:bitcoin_tracker
for projektdækkende filer og historical_data
indeholdende filer til den app, du har oprettet.
For at oprette en model skal du tilføje denne klasse 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()
Dette er den grundlæggende model til at holde styr på Bitcoin-priserne.
Glem heller ikke at tilføje den nyoprettede app til settings.INSTALLED_APPS
. Åbn bitcoin_tracker/settings.py
og tilføj historical_data
til listen INSTALLED_APPS
, sådan her:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'historical_data',
]
De andre indstillinger er fine til dette projekt. Denne vejledning antager, at dit projekt er konfigureret til at bruge en SQLite-database, som er standard.
Oprettelse af migreringer
Med modellen oprettet, er den første ting, du skal gøre, at oprette en migrering til den. Du kan gøre dette med følgende kommando:
$ python manage.py makemigrations historical_data
Migrations for 'historical_data':
historical_data/migrations/0001_initial.py
- Create model PriceHistory
Bemærk: Angivelse af navnet på applikationen, historical_data
, er valgfrit. Hvis du udelader det, oprettes migreringer for alle apps.
Dette opretter migreringsfilen, der instruerer Django om, hvordan man opretter databasetabellerne for de modeller, der er defineret i din applikation. Lad os se på bibliotekstræet igen:
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, er migrations
biblioteket indeholder nu en ny fil:0001_initial.py
.
Bemærk: Du bemærker måske, at du kører makemigrations
kommandoen oprettede også filen db.sqlite3
, som indeholder din SQLite-database.
Når du forsøger at få adgang til en ikke-eksisterende SQLite3-databasefil, oprettes den automatisk.
Denne adfærd er unik for SQLite3. Hvis du bruger en anden database-backend som PostgreSQL eller MySQL, skal du selv oprette databasen før kører makemigrations
.
Du kan tage et kig på databasen med dbshell
ledelseskommando. I SQLite er kommandoen til at liste alle tabeller simpelthen .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 er stadig tom. Det ændrer sig, når du anvender migreringen. Skriv .quit
for at afslutte SQLite-skallen.
Anvendelse af migreringer
Du har nu oprettet migreringen, men for rent faktisk at foretage ændringer i databasen, skal du anvende den med ledelseskommandoen 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
Der sker meget her! Ifølge outputtet er din migrering blevet anvendt. Men hvor kommer alle de andre migrationer fra?
Husk indstillingen INSTALLED_APPS
? Nogle af de andre apps, der er anført der, kommer også med migreringer og migrate
management-kommandoen anvender som standard migreringerne for alle installerede apps.
Kig igen på databasen:
$ 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 er der flere borde. Deres navne giver dig en idé om deres formål. Migreringen, som du genererede i det forrige trin, har skabt historical_data_pricehistory
bord. Lad os inspicere det ved hjælp af .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
kommandoen udskriver CREATE
erklæring, som du ville udføre for at oprette tabellen. Parameteren --indent
formaterer det fint. Selvom du ikke er bekendt med SQL-syntaks, kan du se, at skemaet for historical_data_pricehistory
tabel afspejler felterne i PriceHistory
model.
Der er en kolonne for hvert felt og en ekstra kolonne id
for den primære nøgle, som Django opretter automatisk, medmindre du udtrykkeligt angiver en primær nøgle i din model.
Her er, hvad der sker, hvis du kører migrate
kommando igen:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Ikke noget! Django husker, hvilke migreringer der allerede er blevet anvendt og forsøger ikke at køre dem igen.
Det er værd at bemærke, at du også kan begrænse migrate
ledelseskommando til en enkelt 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, anvender Django nu kun migreringer for historical_data
app.
Når du kører migreringerne for første gang, er det en god idé at anvende alle migreringer for at sikre, at din database indeholder de nødvendige tabeller for de funktioner, du kan tage for givet, såsom brugergodkendelse og sessioner.
Ændring af modeller
Dine modeller er ikke hugget i sten. Dine modeller vil ændre sig, efterhånden som dit Django-projekt får flere funktioner. Du kan tilføje eller fjerne felter eller ændre deres typer og muligheder.
Når du ændrer definitionen af en model, skal databasetabellerne, der bruges til at gemme disse modeller, også ændres. Hvis dine modeldefinitioner ikke matcher dit nuværende databaseskema, vil du højst sandsynligt løbe ind i en django.db.utils.OperationalError
.
Så hvordan ændrer du databasetabellerne? Ved at oprette og anvende en migrering.
Mens du tester din Bitcoin-tracker, indser du, at du har lavet en fejl. Folk sælger brøkdele af en Bitcoin, så feltet volume
skal være af typen DecimalField
i stedet for PositiveIntegerField
.
Lad os ændre modellen til at se sådan ud:
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)
Uden migreringer ville du skulle finde ud af SQL-syntaksen for at lave et PositiveIntegerField
ind i et DecimalField
. Heldigvis klarer Django det for dig. Bare fortæl den at foretage migreringer:
$ python manage.py makemigrations
Migrations for 'historical_data':
historical_data/migrations/0002_auto_20181112_1950.py
- Alter field volume on pricehistory
Bemærk: Navnet på migreringsfilen (0002_auto_20181112_1950.py
) er baseret på det aktuelle tidspunkt og vil være anderledes, hvis du følger med på dit system.
Nu anvender du denne migrering til din database:
$ 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 er blevet anvendt med succes, så du kan bruge dbshell
for at bekræfte, at ændringerne havde en 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
);
Hvis du sammenligner det nye skema med det skema, du så tidligere, vil du bemærke, at typen af volume
kolonne er ændret fra integer
til decimal
for at afspejle ændringen af volume
felt i modellen fra PositiveIntegerField
til DecimalField
.
Udføring af migreringer
Hvis du vil vide, hvilke migrationer der findes i et Django-projekt, behøver du ikke at grave igennem migrations
mapper over dine installerede apps. Du kan bruge 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
Dette viser alle apps i projektet og de migreringer, der er knyttet til hver app. Det vil også sætte et stort X
ud for de migreringer, der allerede er blevet anvendt.
For vores lille eksempel, showmigrations
kommandoen er ikke særlig spændende, men den er praktisk, når du begynder at arbejde på en eksisterende kodebase eller arbejder i et team, hvor du ikke er den eneste person, der tilføjer migreringer.
Ikke-anvendelse af migreringer
Nu ved du, hvordan du foretager ændringer i dit databaseskema ved at oprette og anvende migreringer. På et tidspunkt vil du måske fortryde ændringer og skifte tilbage til et tidligere databaseskema, fordi du:
- Vil du teste en migrering, som en kollega skrev
- Indse, at en ændring, du har foretaget, var en dårlig idé
- Arbejd på flere funktioner med forskellige databaseændringer parallelt
- Ønsker at gendanne en sikkerhedskopi, der blev oprettet, da databasen stadig havde et ældre skema
Heldigvis behøver migrationer ikke at være en ensrettet gade. I mange tilfælde kan virkningerne af en migrering fortrydes ved at annullere en migrering. For at annullere en migrering skal du kalde migrate
med navnet på appen og navnet på migreringen før den migrering, du vil annullere.
Hvis du vil gendanne migreringen 0002_auto_20181112_1950
i dine historical_data
app, skal du bestå 0001_initial
som et argument til 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 er ikke blevet anvendt, hvilket betyder, at ændringerne til databasen er blevet vendt tilbage.
Fjernelse af anvendelsen af en migrering fjerner ikke dens migreringsfil. Næste gang du kører migrate
kommando, vil migreringen blive anvendt igen.
Forsigtig: Forveksle ikke uanvendelige migreringer med den fortryd-handling, du er vant til fra din foretrukne teksteditor.
Ikke alle databaseoperationer kan vendes fuldstændigt tilbage. Hvis du fjerner et felt fra en model, opretter en migrering og anvender det, vil Django fjerne den respektive kolonne fra databasen.
Hvis du fjerner denne migrering, genoprettes kolonnen, men det vil ikke bringe de data tilbage, der blev gemt i den kolonne!
Når du har med migreringsnavne at gøre, sparer Django dig for et par tastetryk ved ikke at tvinge dig til at stave hele navnet på migreringen. Det behøver lige nok af navnet til at identificere det entydigt.
I det forrige eksempel ville det have været nok at køre python manage.py migrate historical_data 0001
.
Navngivningsmigreringer
I ovenstående eksempel fandt Django på et navn til migreringen baseret på tidsstemplet - noget i stil med *0002_auto_20181112_1950
. Hvis du ikke er tilfreds med det, kan du bruge --name
parameter for at angive et brugerdefineret navn (uden .py
udvidelse).
For at prøve det, skal du først fjerne den gamle migrering. Du har allerede fjernet den, så du kan trygt slette filen:
$ rm historical_data/migrations/0002_auto_20181112_1950.py
Nu kan du genskabe det med et mere beskrivende navn:
$ ./manage.py makemigrations historical_data --name switch_to_decimals
Dette vil skabe den samme migrering som før undtagen med det nye navn 0002_switch_to_decimals
.
Konklusion
Du dækkede en del af jorden i dette selvstudie og lærte det grundlæggende i Django-migrering.
For at opsummere ser de grundlæggende trin til at bruge Django-migreringer sådan ud:
- Opret eller opdater en model
- Kør
./manage.py makemigrations <app_name>
- Kør
./manage.py migrate
for at migrere alt eller./manage.py migrate <app_name>
for at migrere en individuel app - Gentag efter behov
Det er det! Denne arbejdsgang vil fungere det meste af tiden, men hvis tingene ikke fungerer som forventet, ved du også, hvordan du angiver og annullerer migreringer.
Hvis du tidligere har oprettet og ændret dine databasetabeller med håndskrevet SQL, er du nu blevet meget mere effektiv ved at uddelegere dette arbejde til Django-migreringer.
I det næste selvstudie i denne serie vil du grave dybere ned i emnet og lære, hvordan Django Migrations fungerer under hætten.
Gratis bonus: Klik her for at få adgang til en gratis Django Learning Resources Guide (PDF), der viser dig tips og tricks samt almindelige faldgruber, du skal undgå, når du bygger Python + Django-webapplikationer.
Skål!