Migreringar¶
Migreringar är Djangos sätt att sprida ändringar som du gör i dina modeller (lägga till ett fält, ta bort en modell etc.) till ditt databasschema. De är utformade för att vara mestadels automatiska, men du måste veta när du ska göra migreringar, när du ska köra dem och de vanliga problem du kan stöta på.
Kommandon¶
Det finns flera kommandon som du kommer att använda för att interagera med migreringar och Djangos hantering av databasscheman:
migrate
, som ansvarar för att applicera och avapplicera migreringar.makemigrations
, som ansvarar för att skapa nya migreringar baserat på de ändringar du har gjort i dina modeller.sqlmigrate
, som visar SQL-satserna för en migrering.showmigrations
, som listar ett projekts migreringar och deras status.
Du bör tänka på migreringar som ett versionskontrollsystem för ditt databasschema. makemigrations
ansvarar för att paketera dina modelländringar i enskilda migreringsfiler - analogt med commits - och migrate
ansvarar för att tillämpa dem på din databas.
Migreringsfilerna för varje app finns i en ”migrations”-katalog inuti appen och är utformade för att läggas till och distribueras som en del av dess kodbas. Du bör göra dem en gång på din utvecklingsmaskin och sedan köra samma migreringar på dina kollegors maskiner, dina staging-maskiner och så småningom dina produktionsmaskiner.
Observera
Det är möjligt att åsidosätta namnet på paketet som innehåller migreringarna för varje enskild app genom att ändra inställningen MIGRATION_MODULES
.
Migreringar körs på samma sätt på samma dataset och ger konsekventa resultat, vilket innebär att det du ser i utveckling och staging under samma omständigheter är exakt vad som kommer att hända i produktion.
Django kommer att göra migreringar för alla ändringar av dina modeller eller fält - även alternativ som inte påverkar databasen - eftersom det enda sättet att rekonstruera ett fält korrekt är att ha alla ändringar i historiken, och du kan behöva dessa alternativ i vissa datamigreringar senare (till exempel om du har ställt in anpassade validerare).
Support för backend¶
Migreringar stöds på alla backends som Django levereras med, liksom alla backends från tredje part om de har programmerat in stöd för schemaändring (görs via SchemaEditor-klassen).
Vissa databaser är dock mer kapabla än andra när det gäller schemamigreringar; några av förbehållen tas upp nedan.
PostgreSQL¶
PostgreSQL är den mest kapabla av alla databaser här när det gäller schemastöd.
MySQL¶
MySQL saknar stöd för transaktioner kring schemaändringsoperationer, vilket innebär att om en migrering misslyckas med att tillämpas måste du manuellt plocka bort ändringarna för att försöka igen (det är omöjligt att rulla tillbaka till en tidigare punkt).
MySQL 8.0 introducerade betydande prestandaförbättringar för DDL-operationer, vilket gör dem mer effektiva och minskar behovet av fullständiga tabellåteruppbyggnader. Det kan dock inte garantera en fullständig frånvaro av lås eller avbrott. I situationer där lås fortfarande är nödvändiga kommer varaktigheten för dessa operationer att stå i proportion till antalet rader som är inblandade.
Slutligen har MySQL en relativt liten gräns för den kombinerade storleken på alla kolumner som ett index täcker. Detta innebär att index som är möjliga på andra backends inte kommer att kunna skapas under MySQL.
SQLite¶
SQLite har mycket lite inbyggt stöd för schemaändring, och därför försöker Django emulera det genom att:
Skapa en ny tabell med det nya schemat
Kopiering av data över
Ta bort det gamla bordet
Byt namn på den nya tabellen så att den matchar det ursprungliga namnet
Den här processen fungerar i allmänhet bra, men den kan vara långsam och ibland buggig. Det rekommenderas inte att du kör och migrerar SQLite i en produktionsmiljö om du inte är mycket medveten om riskerna och dess begränsningar; det stöd som Django levereras med är utformat för att tillåta utvecklare att använda SQLite på sina lokala maskiner för att utveckla mindre komplexa Django-projekt utan behov av en fullständig databas.
Arbetsflöde¶
Django kan skapa migreringar åt dig. Gör ändringar i dina modeller - lägg till exempel till ett fält och ta bort en modell - och kör sedan makemigrations
:
$ python manage.py makemigrations
Migrations for 'books':
books/migrations/0003_auto.py:
~ Alter field author on book
Dina modeller kommer att skannas och jämföras med de versioner som för närvarande finns i dina migreringsfiler, och sedan kommer en ny uppsättning migreringar att skrivas ut. Se till att läsa utdata för att se vad makemigrations
tror att du har ändrat - den är inte perfekt, och för komplexa ändringar kanske den inte upptäcker vad du förväntar dig.
När du har dina nya migreringsfiler bör du tillämpa dem på din databas för att se till att de fungerar som förväntat:
$ python manage.py migrate
Operations to perform:
Apply all migrations: books
Running migrations:
Rendering model states... DONE
Applying books.0003_auto... OK
När migreringen har tillämpats ska du överföra migreringen och modelländringen till ditt versionshanteringssystem som en enda överföring - på så sätt får andra utvecklare (eller dina produktionsservrar) både ändringarna i dina modeller och den åtföljande migreringen samtidigt när de kontrollerar koden.
Om du vill ge migreringarna ett meningsfullt namn i stället för ett genererat namn kan du använda alternativet makemigrations --name
:
$ python manage.py makemigrations --name changed_my_model your_app_label
Versionskontroll¶
Eftersom migreringar lagras i versionshanteringen kan du ibland stöta på situationer där du och en annan utvecklare har genomfört en migrering till samma app samtidigt, vilket resulterar i två migreringar med samma nummer.
Oroa dig inte - siffrorna är bara där för utvecklarnas referens, Django bryr sig bara om att varje migrering har ett annat namn. Migreringar anger vilka andra migreringar de är beroende av - inklusive tidigare migreringar i samma app - i filen, så det är möjligt att upptäcka när det finns två nya migreringar för samma app som inte är ordnade.
När detta händer kommer Django att fråga dig och ge dig några alternativ. Om det tycker att det är tillräckligt säkert kommer det att erbjuda att automatiskt linearisera de två migreringarna åt dig. Om inte, måste du gå in och modifiera migreringarna själv - oroa dig inte, det är inte svårt och förklaras mer i Migreringsfiler nedan.
Transaktioner¶
På databaser som stöder DDL-transaktioner (SQLite och PostgreSQL) kommer alla migreringsoperationer att köras i en enda transaktion som standard. Om en databas däremot inte stöder DDL-transaktioner (t.ex. MySQL, Oracle) kommer alla operationer att köras utan en transaktion.
Du kan förhindra att en migrering körs i en transaktion genom att ställa in attributet atomic
till False
. Till exempel:
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
Det är också möjligt att utföra delar av migreringen inuti en transaktion med atomic()
eller genom att skicka atomic=True
till RunPython
. Se Icke-atomiska migreringar för mer information.
Beroenden¶
Även om migreringar sker per app är tabellerna och relationerna i dina modeller för komplexa för att skapas för en app i taget. När du gör en migrering som kräver att något annat körs - till exempel om du lägger till en ForeignKey
i appen books
till appen authors
- kommer den resulterande migreringen att innehålla ett beroende av en migrering i authors
.
Detta innebär att när du kör migreringarna körs authors
-migreringen först och skapar tabellen som ForeignKey
refererar till, och sedan körs migreringen som skapar ForeignKey
-kolumnen efteråt och skapar begränsningen. Om detta inte hände skulle migreringen försöka skapa kolumnen ForeignKey
utan att tabellen som den hänvisar till existerar och din databas skulle ge ett fel.
Detta beroendebeteende påverkar de flesta migreringar där du begränsar till en enda app. Att begränsa till en enda app (antingen i makemigrations
eller migrate
) är ett löfte om bästa möjliga ansträngning och inte en garanti; alla andra appar som behöver användas för att få beroenden korrekta kommer att bli det.
Appar utan migreringar får inte ha relationer (ForeignKey
, ManyToManyField
, etc.) till appar med migreringar. Ibland kan det fungera, men det stöds inte.
Utbytbara beroenden¶
- django.db.migrations.swappable_dependency(value)¶
Funktionen swappable_dependency()
används i migreringar för att deklarera ”swappable” beroenden av migreringar i appen för den inbytta modellen, för närvarande vid den första migreringen av denna app. Som en följd av detta bör den inbytta modellen skapas i den första migreringen. Argumentet value
är en sträng "<app label>.<model>"
som beskriver en appetikett och ett modellnamn, t.ex. "myapp.MyModel"
.
Genom att använda `swappable_dependency()
informerar du migreringsramverket om att migreringen är beroende av en annan migrering som skapar en swappable-modell, vilket ger möjlighet att ersätta modellen med en annan implementering i framtiden. Detta används vanligtvis för att referera till modeller som är föremål för anpassning eller ersättning, till exempel den anpassade användarmodellen (ettings.AUTH_USER_MODEL
, som standard är "auth.User"
) i Djangos autentiseringssystem.
Migreringsfiler¶
Migreringar lagras i ett diskformat som här kallas ”migreringsfiler”. Dessa filer är egentligen vanliga Python-filer med en överenskommen objektlayout, skrivna i en deklarativ stil.
En grundläggande migreringsfil ser ut så här:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("migrations", "0001_initial")]
operations = [
migrations.DeleteModel("Tribble"),
migrations.AddField("Author", "rating", models.IntegerField(default=0)),
]
Vad Django letar efter när den laddar en migreringsfil (som en Python-modul) är en underklass av django.db.migrations.Migration
som heter Migration
. Det inspekterar sedan detta objekt för fyra attribut, varav endast två används för det mesta:
dependencies
, en lista över migreringar som den här beror på.operations
, en lista överOperation
-klasser som definierar vad denna migrering gör.
Operationerna är nyckeln; de är en uppsättning deklarativa instruktioner som talar om för Django vilka schemaändringar som behöver göras. Django skannar dem och bygger en representation i minnet av alla schemaändringar för alla appar och använder detta för att generera SQL som gör schemaändringarna.
Den minnesstrukturen används också för att ta reda på vad skillnaderna är mellan dina modeller och det aktuella läget för dina migreringar; Django kör igenom alla ändringar, i ordning, på en minnesuppsättning av modeller för att komma fram till läget för dina modeller förra gången du körde makemigrations
. Den använder sedan dessa modeller för att jämföra med dem i dina models.py
-filer för att räkna ut vad du har ändrat.
Du behöver sällan eller aldrig redigera migreringsfiler för hand, men det är fullt möjligt att skriva dem manuellt om du behöver. Vissa av de mer komplexa operationerna kan inte autodetekteras och är endast tillgängliga via en handskriven migrering, så var inte rädd för att redigera dem om du måste.
Anpassade fält¶
Du kan inte ändra antalet positionella argument i ett redan migrerat anpassat fält utan att det uppstår ett TypeError
. Den gamla migreringen kommer att anropa den modifierade metoden __init__
med den gamla signaturen. Så om du behöver ett nytt argument, skapa ett nyckelordsargument och lägg till något i stil med assert 'argument_name' in kwargs
i konstruktören.
Modell chefer¶
Du kan valfritt serialisera chefer till migreringar och ha dem tillgängliga i RunPython
-operationer. Detta görs genom att definiera ett use_in_migrations
-attribut på managerklassen:
class MyManager(models.Manager):
use_in_migrations = True
class MyModel(models.Model):
objects = MyManager()
Om du använder funktionen from_queryset()
för att dynamiskt generera en managerklass måste du ärva från den genererade klassen för att göra den importerbar:
class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
use_in_migrations = True
class MyModel(models.Model):
objects = MyManager()
Se anteckningarna om Historiska modeller i migreringar för att se vilka konsekvenser som följer med.
Inledande migreringar¶
- Migration.initial¶
De ”initiala migreringarna” för en app är de migreringar som skapar den första versionen av appens tabeller. Vanligtvis har en app en första migrering, men i vissa fall med komplexa modellberoenden kan den ha två eller fler.
Initiala migreringar markeras med ett klassattribut initial = True
på migreringsklassen. Om klassattributet initial
inte hittas betraktas en migrering som ”initial” om den är den första migreringen i appen (dvs. om den inte har några beroenden till någon annan migrering i samma app).
När alternativet migrate --fake-initial
används, behandlas dessa initiala migreringar speciellt. För en initial migrering som skapar en eller flera tabeller (CreateModel
operation), kontrollerar Django att alla dessa tabeller redan finns i databasen och tillämpar migreringen om så är fallet. På samma sätt, för en initial migrering som lägger till ett eller flera fält (AddField
operation), kontrollerar Django att alla respektive kolumner redan finns i databasen och fejkapplicerar migreringen om så är fallet. Utan --fake-initial
behandlas initiala migreringar inte annorlunda än någon annan migrering.
Historisk konsekvens¶
Som tidigare nämnts kan du behöva linjärisera migreringar manuellt när två utvecklingsgrenar sammanfogas. När du redigerar migreringsberoenden kan du oavsiktligt skapa ett inkonsekvent historiktillstånd där en migrering har tillämpats men vissa av dess beroenden inte har gjort det. Detta är en stark indikation på att beroendena är felaktiga, så Django kommer att vägra att köra migreringar eller göra nya migreringar tills det är åtgärdat. När du använder flera databaser kan du använda metoden allow_migrate()
i database routers för att kontrollera vilka databaser som makemigrations
kontrollerar för konsekvent historik.
Lägga till migreringar i appar¶
Nya appar är förkonfigurerade för att acceptera migreringar, så du kan lägga till migreringar genom att köra makemigrations
när du har gjort några ändringar.
Om din app redan har modeller och databastabeller och inte har migreringar ännu (till exempel om du skapade den mot en tidigare Django-version) måste du konvertera den till att använda migreringar genom att köra:
$ python manage.py makemigrations your_app_label
Detta kommer att skapa en ny initial migrering för din app. Kör nu python manage.py migrate --fake-initial
, och Django kommer att upptäcka att du har en initial migrering och att de tabeller som den vill skapa redan finns, och kommer att markera migreringen som redan tillämpad. (Utan migrate --fake-initial
flaggan skulle kommandot misslyckas eftersom tabellerna som den vill skapa redan existerar)
Observera att detta bara fungerar under förutsättning att två saker inträffar:
Du har inte ändrat dina modeller sedan du skapade deras tabeller. För att migreringar ska fungera måste du göra den första migreringen först och sedan göra ändringar, eftersom Django jämför ändringar mot migreringsfiler, inte databasen.
Du har inte redigerat din databas manuellt - Django kommer inte att kunna upptäcka att din databas inte matchar dina modeller, du kommer bara att få fel när migreringar försöker ändra dessa tabeller.
Återvändande av migration¶
Migreringar kan återställas med migrate
genom att ange numret på den föregående migreringen. Till exempel, för att vända migreringen books.0003
:
$ python manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto... OK
...\> py manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto... OK
Använd namnet zero
om du vill återkalla alla migreringar som gjorts för en app:
$ python manage.py migrate books zero
Operations to perform:
Unapply all migrations: books
Running migrations:
Rendering model states... DONE
Unapplying books.0002_auto... OK
Unapplying books.0001_initial... OK
...\> py manage.py migrate books zero
Operations to perform:
Unapply all migrations: books
Running migrations:
Rendering model states... DONE
Unapplying books.0002_auto... OK
Unapplying books.0001_initial... OK
En migrering är irreversibel om den innehåller några irreversibla åtgärder. Försök att vända sådana migreringar kommer att ge upphov till IrreversibleError
:
$ python manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
...\> py manage.py migrate books 0002
Operations to perform:
Target specific migration: 0002_auto, from books
Running migrations:
Rendering model states... DONE
Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
Historiska modeller¶
När du kör migreringar arbetar Django från historiska versioner av dina modeller som lagras i migreringsfilerna. Om du skriver Python-kod med hjälp av RunPython
, eller om du har allow_migrate
-metoder på dina databasroutrar, måste du använda dessa historiska modellversioner istället för att importera dem direkt.
Varning
Om du importerar modeller direkt i stället för att använda de historiska modellerna kan dina migreringar fungera initialt, men de kommer att misslyckas i framtiden när du försöker köra gamla migreringar igen (vanligtvis när du skapar en ny installation och kör igenom alla migreringar för att skapa databasen).
Detta innebär att problem med historiska modeller kanske inte är omedelbart uppenbara. Om du stöter på den här typen av fel är det OK att redigera migreringen så att den använder de historiska modellerna i stället för direktimport och genomföra dessa ändringar.
Eftersom det är omöjligt att serialisera godtycklig Python-kod kommer dessa historiska modeller inte att ha några anpassade metoder som du har definierat. De kommer dock att ha samma fält, relationer, chefer (begränsat till dem med use_in_migrations = True
) och Meta
-alternativ (även versionerade, så de kan skilja sig från dina nuvarande).
Varning
Detta innebär att du INTE kommer att ha anpassade save()
-metoder som anropas på objekt när du kommer åt dem i migreringar, och du kommer INTE att ha några anpassade konstruktörer eller instansmetoder. Planera på lämpligt sätt!
Hänvisningar till funktioner i fältalternativ som upload_to
och limit_choices_to
och modellhanterardeklarationer med hanterare som har use_in_migrations = True
serialiseras i migreringar, så funktionerna och klasserna måste sparas så länge det finns en migrering som hänvisar till dem. Eventuella custom model fields måste också sparas, eftersom dessa importeras direkt av migreringar.
Dessutom lagras modellens konkreta basklasser som pekare, så du måste alltid ha kvar basklasserna så länge det finns en migration som innehåller en referens till dem. På plussidan är att metoder och managers från dessa basklasser ärvs normalt, så om man absolut behöver tillgång till dessa kan man välja att flytta dem till en superklass.
För att ta bort gamla referenser kan du squash migrations eller, om det inte finns så många referenser, kopiera dem till migreringsfilerna.
Överväganden vid borttagning av modellfält¶
På samma sätt som när det gäller ”referenser till historiska funktioner” i föregående avsnitt kan det uppstå problem om du tar bort anpassade modellfält från ditt projekt eller din tredjepartsapp om de refereras till i gamla migreringar.
För att hjälpa till med den här situationen tillhandahåller Django några modellfältsattribut för att hjälpa till med borttagning av modellfält med hjälp av systemets ramverk för kontroller.
Lägg till attributet system_check_deprecated_details
till ditt modellfält på följande sätt:
class IPAddressField(Field):
system_check_deprecated_details = {
"msg": (
"IPAddressField has been deprecated. Support for it (except "
"in historical migrations) will be removed in Django 1.9."
),
"hint": "Use GenericIPAddressField instead.", # optional
"id": "fields.W900", # pick a unique ID for your field.
}
Efter en utfasningsperiod som du väljer (två eller tre funktionsutgåvor för fält i Django själv), ändra attributet system_check_deprecated_details
till system_check_removed_details
och uppdatera ordlistan på liknande sätt:
class IPAddressField(Field):
system_check_removed_details = {
"msg": (
"IPAddressField has been removed except for support in "
"historical migrations."
),
"hint": "Use GenericIPAddressField instead.",
"id": "fields.E900", # pick a unique ID for your field.
}
Du bör behålla fältets metoder som krävs för att det ska fungera i databasmigreringar, t.ex. __init__()
, deconstruct()
och get_internal_type()
. Behåll detta stubbfält så länge som de migreringar som refererar till fältet existerar. Till exempel, efter att ha krossat migreringar och tagit bort de gamla, bör du kunna ta bort fältet helt och hållet.
Migrering av data¶
Förutom att ändra databasschemat kan du också använda migreringar för att ändra data i själva databasen, i kombination med schemat om du vill.
Migreringar som ändrar data kallas vanligtvis ”datamigreringar”; det är bäst att skriva dem som separata migreringar vid sidan av dina schemamigreringar.
Django kan inte automatiskt generera datamigreringar åt dig, som det gör med schemamigreringar, men det är inte särskilt svårt att skriva dem. Migreringsfiler i Django består av Operations, och den huvudsakliga operationen du använder för datamigreringar är RunPython
.
Börja med att skapa en tom migreringsfil som du kan arbeta utifrån (Django placerar filen på rätt plats, föreslår ett namn och lägger till beroenden åt dig):
python manage.py makemigrations --empty yourappname
Öppna sedan filen; den ska se ut ungefär så här:
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("yourappname", "0001_initial"),
]
operations = []
Nu är allt du behöver göra att skapa en ny funktion och få RunPython
att använda den. RunPython
förväntar sig en callable som sitt argument som tar två argument - det första är en app registry som har de historiska versionerna av alla dina modeller laddade i den för att matcha var i din historia migreringen sitter, och den andra är en SchemaEditor, som du kan använda för att manuellt utföra databasschemaändringar (men se upp, att göra detta kan förvirra migreringsautodetektorn!)
Låt oss skriva en migrering som fyller vårt nya fält name
med de kombinerade värdena för first_name
och last_name
(vi har tagit vårt förnuft till fånga och insett att alla inte har för- och efternamn). Allt vi behöver göra är att använda den historiska modellen och iterera över raderna:
from django.db import migrations
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = f"{person.first_name} {person.last_name}"
person.save()
class Migration(migrations.Migration):
dependencies = [
("yourappname", "0001_initial"),
]
operations = [
migrations.RunPython(combine_names),
]
När det är gjort kan vi köra python manage.py migrate
som vanligt och datamigreringen kommer att köras på plats tillsammans med andra migreringar.
Du kan skicka en andra callable till RunPython
för att köra den logik du vill ska köras när du migrerar bakåt. Om denna callable utelämnas kommer migreringen bakåt att ge upphov till ett undantag.
Åtkomst till modeller från andra appar¶
När du skriver en RunPython
-funktion som använder modeller från andra appar än den där migreringen finns, bör migreringens dependencies
-attribut innehålla den senaste migreringen av varje app som är inblandad, annars kan du få ett fel som liknar: LookupError: No installed app with label 'myappname'
när du försöker hämta modellen i RunPython
-funktionen med hjälp av apps.get_model()
.
I följande exempel har vi en migrering i app1
som behöver använda modeller i app2
. Vi bryr oss inte om detaljerna i move_m1
annat än att den kommer att behöva komma åt modeller från båda apparna. Därför har vi lagt till ett beroende som anger den senaste migreringen av app2
:
class Migration(migrations.Migration):
dependencies = [
("app1", "0001_initial"),
# added dependency to enable using models from app2 in move_m1
("app2", "0004_foobar"),
]
operations = [
migrations.RunPython(move_m1),
]
Mer avancerade migreringar¶
Om du är intresserad av de mer avancerade migreringsoperationerna eller vill kunna skriva dina egna, se migration operations reference och ”how-to” på writing migrations.
Minska antalet migreringar¶
Du uppmuntras att göra migreringar fritt och inte oroa dig för hur många du har; migreringskoden är optimerad för att hantera hundratals åt gången utan större nedgång. Men så småningom kommer du att vilja gå tillbaka från att ha flera hundra migreringar till bara några få, och det är där squashing kommer in i bilden.
Squashing innebär att man reducerar en befintlig uppsättning med många migreringar till en (eller ibland ett fåtal) migreringar som fortfarande representerar samma ändringar.
Django gör detta genom att ta alla dina befintliga migreringar, extrahera deras Operation
och sätta dem alla i ordning, och sedan köra en optimerare över dem för att försöka minska längden på listan - till exempel vet den att CreateModel
och DeleteModel
upphäver varandra, och den vet att AddField
kan rullas in i CreateModel
.
När operationssekvensen har reducerats så mycket som möjligt - hur mycket som är möjligt beror på hur nära sammanflätade dina modeller är och om du har några RunSQL
eller RunPython
operationer (som inte kan optimeras om de inte är markerade som elidable
) - kommer Django sedan att skriva ut det igen i en ny uppsättning migreringsfiler.
Dessa filer är markerade för att säga att de ersätter de tidigare krossade migreringarna, så de kan samexistera med de gamla migreringsfilerna, och Django kommer intelligent att växla mellan dem beroende på var du befinner dig i historiken. Om du fortfarande är halvvägs genom den uppsättning migreringar som du krossade kommer den att fortsätta använda dem tills den når slutet och sedan byta till den krossade historiken, medan nya installationer kommer att använda den nya krossade migreringen och hoppa över alla de gamla.
Detta gör att du kan göra en squash och inte förstöra system som för närvarande är i produktion och som inte är helt uppdaterade ännu. Den rekommenderade processen är att krossa, behålla de gamla filerna, commita och släppa, vänta tills alla system är uppgraderade med den nya versionen (eller om du är ett tredjepartsprojekt, se till att dina användare uppgraderar versioner i ordning utan att hoppa över någon), och sedan ta bort de gamla filerna, commita och göra en andra version.
Kommandot som ligger bakom allt detta är squashmigrations
- skicka appens etikett och migreringsnamnet som du vill squasha upp till, så börjar det arbeta:
$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
- 0001_initial
- 0002_some_change
- 0003_another_change
- 0004_undo_something
Do you wish to proceed? [y/N] y
Optimizing...
Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.py
You should commit this migration but leave the old ones in place;
the new migration will be used for new installs. Once you are sure
all instances of the codebase have applied the migrations you squashed,
you can delete them.
Använd alternativet squashmigrations --squashed-name
om du vill ange namnet på den krossade migreringen i stället för att använda ett autogenererat namn.
Observera att modellberoenden i Django kan bli mycket komplexa, och squashing kan resultera i migreringar som inte körs; antingen feloptimerade (i vilket fall du kan försöka igen med --no-optimize
, men du bör också rapportera ett problem), eller med ett CircularDependencyError
, i vilket fall du kan lösa det manuellt.
För att manuellt lösa ett CircularDependencyError
, bryt ut en av ForeignKeys i den cirkulära beroendeslingan till en separat migrering och flytta beroendet av den andra appen med den. Om du är osäker kan du se hur makemigrations
hanterar problemet när den ombeds skapa helt nya migreringar från dina modeller. I en framtida version av Django kommer squashmigrations
att uppdateras för att försöka lösa dessa fel själv.
När du har krossat din migrering bör du sedan överföra den tillsammans med de migreringar som den ersätter och distribuera denna ändring till alla körande instanser av din applikation och se till att de kör migrate
för att lagra ändringen i sin databas.
Du måste sedan överföra den krossade migreringen till en normal migrering genom att:
Raderar alla migreringsfiler som den ersätter.
Uppdaterar alla migreringar som är beroende av de borttagna migreringarna så att de istället är beroende av den krossade migreringen.
Ta bort attributet
replaces
i klassenMigration
för den krossade migreringen (det är så Django berättar att det är en krossad migrering).
Observera
När du har krossat en migrering ska du inte krossa den krossade migreringen igen förrän du helt har omvandlat den till en normal migrering.
Rensning av referenser till raderade migreringar
Om det är troligt att du kommer att återanvända namnet på en borttagen migrering i framtiden bör du ta bort referenser till den från Djangos migreringstabell med alternativet migrate --prune
.
Serialisering av värden¶
Migrationer är Python-filer som innehåller de gamla definitionerna av dina modeller - för att skriva dem måste Django alltså ta det aktuella tillståndet för dina modeller och serialisera dem till en fil.
Django kan serialisera det mesta, men det finns vissa saker som vi helt enkelt inte kan serialisera ut till en giltig Python-representation - det finns ingen Python-standard för hur ett värde kan omvandlas tillbaka till kod (repr()
fungerar bara för grundläggande värden och anger inte importvägar).
Django kan serialisera följande:
int
,float
,bool
,str
,bytes
,None
,NoneType
list
,set
,tuple
,dict
,range
.instanser av
datetime.date
,datetime.time
ochdatetime.datetime
(inklusive de som är tidszonmedvetna)decimal.Decimal
instanserinstanser av
enum.Enum
ochenum.Flag
uuid.UUID
instanserfunctools.partial()
ochfunctools.partialmethod
instanser som har serialiserbarafunc
,args
ochkeywords
värden.Rena och konkreta sökvägsobjekt från
pathlib
. Konkreta sökvägar konverteras till deras rena sökvägsekvivalenter, t.ex.pathlib.PosixPath
tillpathlib.PurePosixPath
.os.PathLike
-instanser, t.ex.os.DirEntry
, som konverteras tillstr
ellerbytes
medos.fspath()
.LazyObject
-instanser som omsluter ett serialiserbart värde.Instanser av uppräkningstyper (t.ex.
TextChoices
ellerIntegerChoices
).Alla Django-fält
Alla funktions- eller metodreferenser (t.ex.
datetime.datetime.today
) (måste finnas i modulens toppnivå)Funktioner kan dekoreras om de förpackas på rätt sätt, dvs. med
functools.wraps()
Dekoratorerna
functools.cache()
ochfunctools.lru_cache()
stöds uttryckligen
Obundna metoder som används inom klassens kropp
Vilken klassreferens som helst (måste finnas i modulens toppnivåscope)
Allt med en anpassad
deconstruct()
-metod (se nedan)
Django kan inte serialisera:
Nästlade klasser
Godtyckliga klassinstanser (t.ex.
MyClass(4.3, 5.7)
)Lambdas
Anpassade serialiserare¶
Du kan serialisera andra typer genom att skriva en anpassad serializer. Till exempel, om Django inte serialiserade Decimal
som standard, skulle du kunna göra så här:
from decimal import Decimal
from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter
class DecimalSerializer(BaseSerializer):
def serialize(self):
return repr(self.value), {"from decimal import Decimal"}
MigrationWriter.register_serializer(Decimal, DecimalSerializer)
Det första argumentet i MigrationWriter.register_serializer()
är en typ eller en iterabel av typer som ska använda serializer.
Metoden serialize()
i din serializer måste returnera en sträng som visar hur värdet ska visas i migreringar och en uppsättning av alla importer som behövs i migreringen.
Lägga till en deconstruct()
-metod¶
Du kan låta Django serialisera dina egna anpassade klassinstanser genom att ge klassen en deconstruct()
-metod. Den tar inga argument och bör returnera en tupel av tre saker (path, args, kwargs)
:
path
bör vara Python-sökvägen till klassen, med klassnamnet inkluderat som sista del (t.ex.myapp.custom_things.MyClass
). Om din klass inte är tillgänglig på den översta nivån i en modul är den inte serialiserbar.args
ska vara en lista med positionella argument som ska skickas till din klass__init__
metod. Allt i denna lista bör i sig vara serialiserbart.kwargs
bör vara en dikt av nyckelordsargument att skicka till din klass__init__
metod. Varje värde bör i sig vara serialiserbart.
Observera
Detta returvärde skiljer sig från metoden deconstruct()
:ref:for custom fields <custom-field-deconstruct-method>
som returnerar en tupel med fyra objekt.
Django kommer att skriva ut värdet som en instansiering av din klass med de angivna argumenten, på samma sätt som det skriver ut referenser till Django-fält.
För att förhindra att en ny migrering skapas varje gång makemigrations
körs, bör du också lägga till en __eq__()
-metod till den dekorerade klassen. Denna funktion kommer att anropas av Djangos migreringsramverk för att upptäcka förändringar mellan tillstånd.
Så länge som alla argument till din klass konstruktör är själva serialiserbara, kan du använda @deconstructible
klassdekoratorn från django.utils.deconstruct
för att lägga till deconstruct()
metoden:
from django.utils.deconstruct import deconstructible
@deconstructible
class MyCustomClass:
def __init__(self, foo=1):
self.foo = foo
...
def __eq__(self, other):
return self.foo == other.foo
Dekoratorn lägger till logik för att fånga upp och bevara argumenten på väg in i din konstruktor och returnerar sedan dessa argument exakt när deconstruct() anropas.
Stöd för flera Django-versioner¶
Om du är underhållare av en tredjepartsapp med modeller kan du behöva skicka migreringar som stöder flera Django-versioner. I så fall bör du alltid köra makemigrations
med den lägsta Django-versionen som du vill stödja.
Migreringssystemet kommer att upprätthålla bakåtkompatibilitet enligt samma policy som resten av Django, så migreringsfiler som genererats på Django X.Y bör köras oförändrade på Django X.Y+1. Migreringssystemet lovar dock inte framåtkompatibilitet. Nya funktioner kan läggas till, och migreringsfiler som genereras med nyare versioner av Django kanske inte fungerar på äldre versioner.
Se även
- Referensen för migreringsoperationer
Omfattar API:et för schemaoperationer, specialoperationer och att skriva egna operationer.
- ”Hur man skriver migreringar”
Förklarar hur man strukturerar och skriver databasmigreringar för olika scenarier som man kan stöta på.