Dit is het tweede artikel in onze serie Django-migraties:
- Deel 1:Django-migraties:een primeur
- Deel 2:dieper graven in Django-migraties (huidig artikel)
- Deel 3:Gegevensmigraties
- Video:Django 1.7-migraties - Een primeur
In het vorige artikel in deze serie heb je geleerd over het doel van Django-migraties. Je bent vertrouwd geraakt met fundamentele gebruikspatronen zoals het maken en toepassen van migraties. Nu is het tijd om dieper in het migratiesysteem te graven en een kijkje te nemen in enkele van de onderliggende mechanica.
Aan het einde van dit artikel weet je:
- Hoe Django migraties bijhoudt
- Hoe migraties weten welke databasebewerkingen moeten worden uitgevoerd
- Hoe afhankelijkheden tussen migraties worden gedefinieerd
Als je eenmaal je hoofd rond dit deel van het Django-migratiesysteem hebt gewikkeld, ben je goed voorbereid om je eigen aangepaste migraties te maken. Laten we meteen verder gaan waar we gebleven waren!
Dit artikel gebruikt de bitcoin_tracker
Django-project gebouwd in Django Migrations:A Primer. Je kunt dat project opnieuw maken door dat artikel door te nemen of je kunt de broncode downloaden:
Broncode downloaden: Klik hier om de code te downloaden voor het Django-migratieproject dat u in dit artikel gaat gebruiken.
Hoe Django weet welke migraties hij moet toepassen
Laten we de allerlaatste stap van het vorige artikel in de serie samenvatten. U heeft een migratie gemaakt en vervolgens alle beschikbare migraties toegepast met python manage.py migrate
.Als die opdracht met succes is uitgevoerd, komen uw databasetabellen nu overeen met de definities van uw model.
Wat gebeurt er als je die opdracht opnieuw uitvoert? Laten we het proberen:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Er is niks gebeurd! Zodra een migratie is toegepast op een database, zal Django deze migratie niet meer toepassen op die specifieke database. Om ervoor te zorgen dat een migratie slechts één keer wordt toegepast, moet u de uitgevoerde migraties bijhouden.
Django gebruikt een databasetabel genaamd django_migrations
. Django maakt deze tabel automatisch aan in uw database wanneer u voor het eerst migraties toepast. Voor elke migratie die wordt toegepast of vervalst, wordt een nieuwe rij in de tabel ingevoegd.
Zo ziet deze tabel er bijvoorbeeld uit in onze bitcoin_tracker
project:
ID | App | Naam | Toegepast |
---|---|---|---|
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 |
Zoals u kunt zien, is er een vermelding voor elke toegepaste migratie. De tabel bevat niet alleen de migraties van onze historical_data
app, maar ook de migraties van alle andere geïnstalleerde apps.
De volgende keer dat migraties worden uitgevoerd, slaat Django de migraties in de databasetabel over. Dit betekent dat, zelfs als u het bestand van een reeds toegepaste migratie handmatig wijzigt, Django deze wijzigingen negeert, zolang er al een vermelding voor in de database is.
Je zou Django kunnen misleiden om een migratie opnieuw uit te voeren door de corresponderende rij uit de tabel te verwijderen, maar dit is zelden een goed idee en kan leiden tot een defect migratiesysteem.
Het migratiebestand
Wat gebeurt er als je python manage.py makemigrations <appname>
uitvoert ? Django zoekt naar wijzigingen die zijn aangebracht in de modellen in uw app <appname>
. Als het er een vindt, zoals een toegevoegd model, dan maakt het een migratiebestand aan in de migrations
submap. Dit migratiebestand bevat een lijst met bewerkingen om uw databaseschema in overeenstemming te brengen met uw modeldefinitie.
Opmerking: Uw app moet worden vermeld in de INSTALLED_APPS
instelling, en het moet een migrations
. bevatten directory met een __init__.py
het dossier. Anders zal Django er geen migraties voor maken.
De migrations
directory wordt automatisch aangemaakt wanneer u een nieuwe app maakt met de startapp
beheeropdracht, maar het is gemakkelijk te vergeten wanneer u handmatig een app maakt.
De migratiebestanden zijn gewoon Python, dus laten we eens kijken naar het eerste migratiebestand in de historical_prices
app. Je kunt het vinden op historical_prices/migrations/0001_initial.py
. Het zou er ongeveer zo uit moeten zien:
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,),
),
]
Zoals je kunt zien, bevat het een enkele klasse genaamd Migration
dat erft van django.db.migrations.Migration
. Dit is de klasse die het migratieraamwerk zoekt en uitvoert wanneer u het vraagt om migraties toe te passen.
De Migration
class bevat twee hoofdlijsten:
dependencies
operations
Migratiebewerkingen
Laten we eens kijken naar de operations
lijst eerst. Deze tabel bevat de bewerkingen die moeten worden uitgevoerd als onderdeel van de migratie. Bewerkingen zijn subklassen van de klasse django.db.migrations.operations.base.Operation
. Dit zijn de algemene bewerkingen die in Django zijn ingebouwd:
Operatieklasse | Beschrijving |
---|---|
CreateModel | Maakt een nieuw model en de bijbehorende databasetabel |
DeleteModel | Verwijdert een model en verwijdert de databasetabel |
RenameModel | Hernoemt een model en hernoemt de databasetabel |
AlterModelTable | Hernoemt de databasetabel voor een model |
AlterUniqueTogether | Verandert de unieke beperkingen van een model |
AlterIndexTogether | Wijzigt de indexen van een model |
AlterOrderWithRespectTo | Maakt of verwijdert de _order kolom voor een model |
AlterModelOptions | Verandert verschillende modelopties zonder de database te beïnvloeden |
AlterModelManagers | Wijzigt de beschikbare managers tijdens migraties |
AddField | Voegt een veld toe aan een model en de corresponderende kolom in de database |
RemoveField | Verwijdert een veld uit een model en verwijdert de corresponderende kolom uit de database |
AlterField | Verandert de definitie van een veld en wijzigt indien nodig de databasekolom |
RenameField | Hernoemt een veld en, indien nodig, ook de databasekolom |
AddIndex | Maakt een index in de databasetabel voor het model |
RemoveIndex | Verwijdert een index uit de databasetabel voor het model |
Merk op hoe de bewerkingen zijn vernoemd naar wijzigingen die zijn aangebracht in modeldefinities, niet de acties die op de database worden uitgevoerd. Wanneer u een migratie toepast, is elke bewerking verantwoordelijk voor het genereren van de benodigde SQL-instructies voor uw specifieke database. Bijvoorbeeld CreateModel
zou een CREATE TABLE
. genereren SQL-instructie.
Out of the box, migraties hebben ondersteuning voor alle standaard databases die Django ondersteunt. Dus als u zich aan de hier vermelde bewerkingen houdt, kunt u min of meer wijzigingen aanbrengen in uw modellen die u wilt, zonder dat u zich zorgen hoeft te maken over de onderliggende SQL. Dat wordt allemaal voor u gedaan.
Opmerking: In sommige gevallen detecteert Django uw wijzigingen mogelijk niet correct. Als je een model hernoemt en verschillende velden wijzigt, kan Django dit voor een nieuw model aanzien.
In plaats van een RenameModel
en verschillende AlterField
bewerkingen, maakt het een DeleteModel
en een CreateModel
operatie. In plaats van de databasetabel voor het model te hernoemen, wordt deze verwijderd en een nieuwe tabel met de nieuwe naam gemaakt, waardoor al uw gegevens effectief worden verwijderd!
Maak er een gewoonte van om de gegenereerde migraties te controleren en te testen op een kopie van uw database voordat u ze uitvoert op productiegegevens.
Django biedt nog drie bedieningsklassen voor geavanceerde gebruiksscenario's:
RunSQL
stelt u in staat om aangepaste SQL in de database uit te voeren.RunPython
stelt u in staat om elke Python-code uit te voeren.SeparateDatabaseAndState
is een gespecialiseerde operatie voor geavanceerd gebruik.
Met deze bewerkingen kunt u in principe alle gewenste wijzigingen in uw database aanbrengen. U vindt deze bewerkingen echter niet in een migratie die automatisch is gemaakt met de makemigrations
management commando.
Sinds Django 2.0 zijn er ook een aantal PostgreSQL-specifieke bewerkingen beschikbaar in django.contrib.postgres.operations
die u kunt gebruiken om verschillende PostgreSQL-extensies te installeren:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Houd er rekening mee dat een migratie met een van deze bewerkingen een databasegebruiker met superuser-rechten vereist.
Last but not least kunt u ook uw eigen bedieningsklassen maken. Als je dat wilt onderzoeken, bekijk dan de Django-documentatie over het maken van aangepaste migratiebewerkingen.
Migratieafhankelijkheden
De dependencies
lijst in een migratieklasse bevat alle migraties die moeten worden toegepast voordat deze migratie kan worden toegepast.
In de 0001_initial.py
migratie die u hierboven hebt gezien, hoeft niets vooraf te worden toegepast, dus er zijn geen afhankelijkheden. Laten we eens kijken naar de tweede migratie in de historical_prices
app. In het bestand 0002_switch_to_decimals.py
, de dependencies
attribuut van Migration
heeft een vermelding:
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),
),
]
De bovenstaande afhankelijkheid zegt dat migratie 0001_initial
van de app historical_data
moet eerst worden uitgevoerd. Dat is logisch, want de migratie 0001_initial
maakt de tabel met het veld dat de migratie 0002_switch_to_decimals
wil veranderen.
Een migratie kan ook afhankelijk zijn van een migratie vanuit een andere app, zoals deze:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Dit is meestal nodig als een model een externe sleutel heeft die naar een model in een andere app verwijst.
Als alternatief kunt u ook afdwingen dat een migratie vóór . wordt uitgevoerd een andere migratie met het kenmerk run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Afhankelijkheden kunnen ook worden gecombineerd, zodat u meerdere afhankelijkheden kunt hebben. Deze functionaliteit biedt veel flexibiliteit, omdat u externe sleutels kunt accommoderen die afhankelijk zijn van modellen van verschillende apps.
De optie om afhankelijkheden tussen migraties expliciet te definiëren betekent ook dat de nummering van de migraties (meestal 0001
, 0002
, 0003
, ...) geeft niet strikt de volgorde weer waarin migraties worden toegepast. U kunt elke gewenste afhankelijkheid toevoegen en zo de volgorde beheren zonder alle migraties opnieuw te nummeren.
De migratie bekijken
Over het algemeen hoeft u zich geen zorgen te maken over de SQL die migraties genereren. Maar als je wilt controleren of de gegenereerde SQL klopt of gewoon nieuwsgierig bent hoe het eruit ziet, dan heeft Django je gedekt met de sqlmigrate
management commando:
$ 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;
Als u dat doet, worden de onderliggende SQL-query's weergegeven die worden gegenereerd door de opgegeven migratie, op basis van de database in uw settings.py
het dossier. Wanneer u de parameter --backwards
pass doorgeeft , Django genereert de SQL om de migratie ongedaan te maken:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Zodra u de uitvoer van sqlmigrate
ziet, voor een iets complexere migratie, zult u begrijpen dat u niet al deze SQL met de hand hoeft te maken!
Hoe Django veranderingen aan uw modellen detecteert
Je hebt gezien hoe een migratiebestand eruitziet en hoe de lijst met Operation
klassen definieert de wijzigingen die in de database zijn uitgevoerd. Maar hoe weet Django precies welke bewerkingen in een migratiebestand moeten komen? Je zou verwachten dat Django je modellen vergelijkt met je databaseschema, maar dat is niet het geval.
Bij het uitvoeren van makemigrations
, Django doet niet inspecteer uw database. Het vergelijkt uw modelbestand ook niet met een eerdere versie. In plaats daarvan doorloopt Django alle migraties die zijn toegepast en bouwt hij een projectstatus op van hoe de modellen eruit zouden moeten zien. Deze projectstatus wordt vervolgens vergeleken met uw huidige modeldefinities en er wordt een lijst met bewerkingen gemaakt, die, indien toegepast, de projectstatus zou bijwerken met de modeldefinities.
Schaken met Django
Je kunt je modellen zien als een schaakbord, en Django is een schaakgrootmeester die toekijkt hoe je tegen jezelf speelt. Maar de grootmeester let niet op elke beweging. De grootmeester kijkt alleen naar het bord als je makemigrations
. roept .
Omdat er maar een beperkt aantal mogelijke zetten is (en de grootmeester een grootmeester is), kan ze de zetten bedenken die zijn gebeurd sinds ze voor het laatst naar het bord heeft gekeken. Ze maakt wat aantekeningen en laat je spelen totdat je makemigrations
. roept nogmaals.
Als de grootmeester de volgende keer naar het bord kijkt, herinnert hij zich niet meer hoe het schaakbord er de vorige keer uitzag, maar ze kan haar aantekeningen van de vorige zetten doornemen en een mentaal model bouwen van hoe het schaakbord eruit zag.
Als je nu migrate
. roept , zal de grootmeester alle vastgelegde zetten op een ander schaakbord naspelen en in een spreadsheet noteren welke van haar records al zijn toegepast. Dit tweede schaakbord is uw database en de spreadsheet is de django_migrations
tafel.
Deze analogie is heel passend, omdat het een aantal gedragingen van Django-migraties mooi illustreert:
-
Django-migraties proberen efficiënt te zijn: Net zoals de grootmeester ervan uitgaat dat je het minste aantal zetten hebt gedaan, zal Django proberen de meest efficiënte migraties te maken. Als u een veld met de naam
A
. toevoegt naar een model en hernoem het vervolgens naarB
, en voer vervolgensmakemigrations
uit , dan zal Django een nieuwe migratie maken om een veld toe te voegen met de naamB
. -
Django-migraties hebben hun limieten: Als je veel zetten doet voordat je de grootmeester naar het schaakbord laat kijken, kan ze misschien niet de exacte bewegingen van elk stuk natrekken. Evenzo komt Django mogelijk niet met de juiste migratie als u te veel wijzigingen tegelijk aanbrengt.
-
Django-migratie verwacht dat je je aan de regels houdt: Als je iets onverwachts doet, zoals een willekeurig stuk van het bord halen of knoeien met de noten, zal de grootmeester het in eerste instantie misschien niet opmerken, maar vroeg of laat zal ze haar handen in de lucht steken en weigeren verder te gaan. Hetzelfde gebeurt als je knoeit met de
django_migrations
tabel of wijzig uw databaseschema buiten migraties om, bijvoorbeeld door de databasetabel voor een model te verwijderen.
Inzicht in SeparateDatabaseAndState
Nu je weet wat de projectstatus is die Django bouwt, is het tijd om de bewerking SeparateDatabaseAndState
nader te bekijken. . Deze bewerking kan precies doen wat de naam aangeeft:het kan de projectstatus (het mentale model dat Django bouwt) scheiden van uw database.
SeparateDatabaseAndState
wordt geïnstantieerd met twee lijsten met bewerkingen:
state_operations
bevat bewerkingen die alleen worden toegepast op de projectstatus.database_operations
bevat bewerkingen die alleen worden toegepast op de database.
Met deze bewerking kunt u elke vorm van wijziging aan uw database aanbrengen, maar het is uw verantwoordelijkheid om ervoor te zorgen dat de projectstatus naderhand in de database past. Voorbeelden van gebruiksscenario's voor SeparateDatabaseAndState
een model van de ene app naar de andere verplaatsen of een index maken op een enorme database zonder downtime.
SeparateDatabaseAndState
is een geavanceerde operatie en u hoeft op uw eerste dag niet met migraties te werken en misschien zelfs helemaal niet. SeparateDatabaseAndState
lijkt op een hartoperatie. Het brengt nogal wat risico's met zich mee en is niet iets wat je alleen voor de lol doet, maar soms is het een noodzakelijke procedure om de patiënt in leven te houden.
Conclusie
Dit is het einde van je diepe duik in Django-migraties. Gefeliciteerd! Je hebt heel wat geavanceerde onderwerpen behandeld en je hebt nu een goed begrip van wat er gebeurt onder de motorkap van migraties.
Je hebt geleerd dat:
- Django houdt toegepaste migraties bij in de Django-migratietabel.
- Django-migraties bestaan uit gewone Python-bestanden met een
Migration
klas. - Django weet welke wijzigingen hij moet uitvoeren via de
operations
lijst in deMigration
lessen. - Django vergelijkt uw modellen met een projectstatus die het opbouwt op basis van de migraties.
Met deze kennis bent u nu klaar om het derde deel van de serie over Django-migraties aan te pakken, waarin u leert hoe u gegevensmigraties kunt gebruiken om veilig eenmalige wijzigingen in uw gegevens aan te brengen. Blijf op de hoogte!
Dit artikel gebruikte de bitcoin_tracker
Django-project gebouwd in Django Migrations:A Primer. U kunt dat project opnieuw maken door dat artikel door te nemen of u kunt de broncode downloaden:
Broncode downloaden: Klik hier om de code te downloaden voor het Django-migratieproject dat u in dit artikel gaat gebruiken.