sql >> Database >  >> RDS >> Database

Dieper graven in Django-migraties

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:

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

  1. RunSQL stelt u in staat om aangepaste SQL in de database uit te voeren.
  2. RunPython stelt u in staat om elke Python-code uit te voeren.
  3. 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 naar B , en voer vervolgens makemigrations uit , dan zal Django een nieuwe migratie maken om een ​​veld toe te voegen met de naam B .

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

  1. state_operations bevat bewerkingen die alleen worden toegepast op de projectstatus.
  2. 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 de Migration 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.



  1. Hoe een enkele rij te vergrendelen

  2. Een waarschuwing uitschakelen in sqlalchemy

  3. Spreadsheets versus databases:is het tijd om over te stappen? Deel 1

  4. Hoe bestanden te verwijderen in SQL Server 2019