To jest drugi artykuł z naszej serii migracji Django:
- Część 1:Migracje Django:elementarz
- Część 2:Zagłębianie się w migracje Django (aktualny artykuł)
- Część 3:Migracje danych
- Wideo:Migracje Django 1.7 – elementarz
W poprzednim artykule z tej serii dowiedziałeś się o celu migracji Django. Zapoznałeś się z podstawowymi wzorcami użytkowania, takimi jak tworzenie i stosowanie migracji. Teraz nadszedł czas, aby zagłębić się w system migracji i rzucić okiem na niektóre z jego podstawowych mechanizmów.
Pod koniec tego artykułu będziesz wiedzieć:
- Jak Django śledzi migracje
- Skąd migracje wiedzą, jakie operacje na bazie danych wykonać
- Jak definiowane są zależności między migracjami
Gdy już opanujesz tę część systemu migracji Django, będziesz dobrze przygotowany do tworzenia własnych, niestandardowych migracji. Przejdźmy dokładnie tam, gdzie skończyliśmy!
Ten artykuł wykorzystuje bitcoin_tracker
Projekt Django zbudowany w Django Migrations:A Primer. Możesz albo odtworzyć ten projekt, pracując nad tym artykułem, albo pobrać kod źródłowy:
Pobierz kod źródłowy: Kliknij tutaj, aby pobrać kod projektu migracji Django, którego będziesz używać w tym artykule.
Skąd Django wie, które migracje należy zastosować
Podsumujmy ostatni krok poprzedniego artykułu z tej serii. Utworzyłeś migrację, a następnie zastosowałeś wszystkie dostępne migracje za pomocą python manage.py migrate
.Jeśli to polecenie zadziałało pomyślnie, tabele bazy danych odpowiadają teraz definicjom modelu.
Co się stanie, jeśli ponownie uruchomisz to polecenie? Wypróbujmy to:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Nic się nie stało! Gdy migracja zostanie zastosowana do bazy danych, Django nie zastosuje tej migracji ponownie do tej konkretnej bazy danych. Zapewnienie, że migracja zostanie zastosowana tylko raz, wymaga śledzenia zastosowanych migracji.
Django używa tabeli bazy danych o nazwie django_migrations
. Django automatycznie tworzy tę tabelę w Twojej bazie danych przy pierwszym zastosowaniu jakichkolwiek migracji. W przypadku każdej zastosowanej lub sfałszowanej migracji do tabeli wstawiany jest nowy wiersz.
Na przykład, oto jak wygląda ta tabela w naszym bitcoin_tracker
projekt:
ID | Aplikacja | Nazwa | Zastosowano |
---|---|---|---|
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 |
Jak widać, dla każdej zastosowanej migracji istnieje wpis. Tabela zawiera nie tylko migracje z naszych historical_data
aplikacji, ale także migracje ze wszystkich innych zainstalowanych aplikacji.
Przy następnym uruchomieniu migracji Django pominie migracje wymienione w tabeli bazy danych. Oznacza to, że nawet jeśli ręcznie zmienisz plik migracji, która została już zastosowana, Django zignoruje te zmiany, o ile istnieje już wpis w bazie danych.
Możesz nakłonić Django do ponownego uruchomienia migracji, usuwając odpowiedni wiersz z tabeli, ale rzadko jest to dobry pomysł i może spowodować uszkodzenie systemu migracji.
Plik migracji
Co się stanie, gdy uruchomisz python manage.py makemigrations <appname>
? Django szuka zmian wprowadzonych w modelach w Twojej aplikacji <appname>
. Jeśli znajdzie jakieś, takie jak dodany model, tworzy plik migracji w migrations
podkatalog. Ten plik migracji zawiera listę operacji mających na celu zsynchronizowanie schematu bazy danych z definicją modelu.
Uwaga: Twoja aplikacja musi być wymieniona w INSTALLED_APPS
ustawienie i musi zawierać migrations
katalog z __init__.py
plik. W przeciwnym razie Django nie utworzy dla niego żadnych migracji.
migrations
katalog jest tworzony automatycznie podczas tworzenia nowej aplikacji za pomocą startapp
polecenie zarządzania, ale łatwo o tym zapomnieć podczas ręcznego tworzenia aplikacji.
Pliki migracji to tylko Python, więc spójrzmy na pierwszy plik migracji w historical_prices
aplikacja. Możesz go znaleźć pod adresem historical_prices/migrations/0001_initial.py
. Powinno to wyglądać mniej więcej tak:
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,),
),
]
Jak widać, zawiera jedną klasę o nazwie Migration
który dziedziczy z django.db.migrations.Migration
. Jest to klasa, której framework migracji będzie szukał i wykonywał, gdy poprosisz go o zastosowanie migracji.
Migration
klasa zawiera dwie główne listy:
dependencies
operations
Operacje migracyjne
Spójrzmy na operations
lista w pierwszej kolejności. Ta tabela zawiera operacje, które mają zostać wykonane w ramach migracji. Operacje są podklasami klasy django.db.migrations.operations.base.Operation
. Oto typowe operacje wbudowane w Django:
Klasa operacji | Opis |
---|---|
CreateModel | Tworzy nowy model i odpowiednią tabelę bazy danych |
DeleteModel | Usuwa model i upuszcza jego tabelę bazy danych |
RenameModel | Zmienia nazwę modelu i jego tabeli bazy danych |
AlterModelTable | Zmienia nazwę tabeli bazy danych dla modelu |
AlterUniqueTogether | Zmienia unikalne ograniczenia modelu |
AlterIndexTogether | Zmienia indeksy modelu |
AlterOrderWithRespectTo | Tworzy lub usuwa _order kolumna dla modelu |
AlterModelOptions | Zmienia różne opcje modelu bez wpływu na bazę danych |
AlterModelManagers | Zmienia menedżerów dostępnych podczas migracji |
AddField | Dodaje pole do modelu i odpowiednią kolumnę w bazie danych |
RemoveField | Usuwa pole z modelu i usuwa odpowiednią kolumnę z bazy danych |
AlterField | Zmienia definicję pola i w razie potrzeby zmienia kolumnę bazy danych |
RenameField | Zmienia nazwę pola i, jeśli to konieczne, również jego kolumny bazy danych |
AddIndex | Tworzy indeks w tabeli bazy danych dla modelu |
RemoveIndex | Usuwa indeks z tabeli bazy danych dla modelu |
Zwróć uwagę, jak operacje są nazywane po zmianach wprowadzonych w definicjach modelu, a nie po akcjach wykonywanych w bazie danych. Po zastosowaniu migracji każda operacja jest odpowiedzialna za wygenerowanie niezbędnych instrukcji SQL dla określonej bazy danych. Na przykład CreateModel
wygeneruje CREATE TABLE
Instrukcja SQL.
Po wyjęciu z pudełka migracje obsługują wszystkie standardowe bazy danych obsługiwane przez Django. Jeśli więc będziesz trzymać się wymienionych tutaj operacji, możesz dokonać mniej więcej dowolnych zmian w swoich modelach, nie martwiąc się o bazowy kod SQL. To wszystko za Ciebie.
Uwaga: W niektórych przypadkach Django może nie wykryć poprawnie wprowadzonych zmian. Jeśli zmienisz nazwę modelu i zmienisz kilka jego pól, Django może pomylić to z nowym modelem.
Zamiast RenameModel
i kilka AlterField
operacji, utworzy DeleteModel
i CreateModel
operacja. Zamiast zmieniać nazwę tabeli bazy danych dla modelu, usunie ją i utworzy nową tabelę o nowej nazwie, skutecznie usuwając wszystkie dane!
Wyrób sobie nawyk sprawdzania wygenerowanych migracji i testowania ich na kopii bazy danych przed uruchomieniem ich na danych produkcyjnych.
Django zapewnia trzy dodatkowe klasy operacyjne dla zaawansowanych przypadków użycia:
RunSQL
pozwala na uruchomienie niestandardowego SQL w bazie danych.RunPython
pozwala uruchomić dowolny kod Pythona.SeparateDatabaseAndState
to wyspecjalizowana operacja do zaawansowanych zastosowań.
Dzięki tym operacjom możesz w zasadzie dokonywać dowolnych zmian w swojej bazie danych. Jednak nie znajdziesz tych operacji w migracji, która została utworzona automatycznie za pomocą makemigrations
polecenie zarządzania.
Od Django 2.0 istnieje również kilka operacji specyficznych dla PostgreSQL dostępnych w django.contrib.postgres.operations
którego możesz użyć do zainstalowania różnych rozszerzeń PostgreSQL:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Pamiętaj, że migracja zawierająca jedną z tych operacji wymaga użytkownika bazy danych z uprawnieniami superużytkownika.
Last but not least, możesz także tworzyć własne klasy operacyjne. Jeśli chcesz się temu przyjrzeć, spójrz na dokumentację Django na temat tworzenia niestandardowych operacji migracji.
Zależności migracji
dependencies
lista w klasie migracji zawiera wszelkie migracje, które muszą zostać zastosowane przed zastosowaniem tej migracji.
W 0001_initial.py
migracja, którą widziałeś powyżej, nic nie musi być stosowane wcześniej, więc nie ma żadnych zależności. Rzućmy okiem na drugą migrację w historical_prices
aplikacja. W pliku 0002_switch_to_decimals.py
, dependencies
atrybut Migration
ma wpis:
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),
),
]
Powyższa zależność mówi, że migracja 0001_initial
aplikacji historical_data
musi być uruchomiony jako pierwszy. To ma sens, ponieważ migracja 0001_initial
tworzy tabelę zawierającą pole, które migracja 0002_switch_to_decimals
chce się zmienić.
Migracja może również zależeć od migracji z innej aplikacji, na przykład:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Jest to zwykle konieczne, jeśli model ma klucz obcy wskazujący na model w innej aplikacji.
Możesz też wymusić uruchomienie migracji przed kolejna migracja przy użyciu atrybutu run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Zależności można również łączyć, dzięki czemu można mieć wiele zależności. Ta funkcja zapewnia dużą elastyczność, ponieważ możesz dostosować klucze obce, które zależą od modeli z różnych aplikacji.
Opcja jawnego zdefiniowania zależności między migracjami oznacza również, że numeracja migracji (zwykle 0001
, 0002
, 0003
, …) nie odzwierciedla ściśle kolejności stosowania migracji. Możesz dodać dowolną zależność, a tym samym kontrolować kolejność bez konieczności ponownego numerowania wszystkich migracji.
Wyświetlanie migracji
Generalnie nie musisz się martwić o kod SQL generowany przez migracje. Ale jeśli chcesz dwukrotnie sprawdzić, czy wygenerowany SQL ma sens lub jesteś po prostu ciekaw, jak wygląda, to Django zapewni Ci sqlmigrate
polecenie zarządzania:
$ 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;
Spowoduje to wyświetlenie listy bazowych zapytań SQL, które zostaną wygenerowane przez określoną migrację, na podstawie bazy danych w pliku settings.py
plik. Po przekazaniu parametru --backwards
, Django generuje kod SQL, aby anulować migrację:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Gdy zobaczysz wynik sqlmigrate
w przypadku nieco bardziej złożonej migracji możesz docenić fakt, że nie musisz ręcznie tworzyć całego tego kodu SQL!
Jak Django wykrywa zmiany w Twoich modelach
Widziałeś, jak wygląda plik migracji i jak jego lista Operation
klasy definiują zmiany dokonywane w bazie danych. Ale skąd dokładnie Django wie, które operacje powinny znaleźć się w pliku migracji? Możesz się spodziewać, że Django porównuje twoje modele ze schematem bazy danych, ale tak nie jest.
Podczas uruchamiania makemigrations
, Django nie sprawdź swoją bazę danych. Nie porównuje też pliku modelu z wcześniejszą wersją. Zamiast tego Django przechodzi przez wszystkie zastosowane migracje i buduje stan projektu, jak powinny wyglądać modele. Ten stan projektu jest następnie porównywany z obecnymi definicjami modeli i tworzona jest lista operacji, które po zastosowaniu uaktualnią stan projektu z definicjami modeli.
Gra w szachy z Django
Możesz myśleć o swoich modelach jak o szachownicy, a Django jest arcymistrzem szachowym, obserwującym, jak grasz przeciwko sobie. Ale arcymistrz nie obserwuje każdego twojego ruchu. Arcymistrz patrzy na tablicę tylko wtedy, gdy krzyczysz makemigrations
.
Ponieważ istnieje tylko ograniczony zestaw możliwych ruchów (a arcymistrz jest arcymistrzem), może wymyślić ruchy, które miały miejsce od ostatniego spojrzenia na planszę. Robi notatki i pozwala ci grać, dopóki nie krzykniesz makemigrations
ponownie.
Gdy następnym razem patrzy na szachownicę, arcymistrz nie pamięta, jak wyglądała szachownica ostatnim razem, ale może przejrzeć swoje notatki z poprzednich ruchów i zbudować mentalny model tego, jak wyglądała szachownica.
Teraz, kiedy krzyczysz migrate
, arcymistrz odtworzy wszystkie zarejestrowane ruchy na innej szachownicy i zapisze w arkuszu kalkulacyjnym, które z jej zapisów zostały już zastosowane. Ta druga szachownica to twoja baza danych, a arkusz kalkulacyjny to django_migrations
tabela.
Ta analogia jest całkiem trafna, ponieważ dobrze ilustruje niektóre zachowania migracji Django:
-
Migracje Django starają się być wydajne: Tak jak arcymistrz zakłada, że wykonałeś najmniej ruchów, tak Django postara się stworzyć najbardziej wydajne migracje. Jeśli dodasz pole o nazwie
A
do modelu, a następnie zmień jego nazwę naB
, a następnie uruchommakemigrations
, wtedy Django utworzy nową migrację, aby dodać pole o nazwieB
. -
Migracje Django mają swoje ograniczenia: Jeśli wykonasz wiele ruchów, zanim pozwolisz arcymistrzowi spojrzeć na szachownicę, może nie być w stanie odtworzyć dokładnych ruchów każdego pionka. Podobnie, Django może nie wymyślić poprawnej migracji, jeśli wprowadzisz zbyt wiele zmian naraz.
-
Migracja Django oczekuje, że będziesz grać zgodnie z zasadami: Kiedy zrobisz coś nieoczekiwanego, jak na przykład zdejmowanie losowego pionka z planszy lub grzebanie w notatkach, arcymistrz może początkowo tego nie zauważyć, ale prędzej czy później podniesie ręce i odmówi kontynuowania. To samo dzieje się, gdy zadzierasz z
django_migrations
tabeli lub zmień schemat bazy danych poza migracją, na przykład usuwając tabelę bazy danych dla modelu.
Zrozumienie SeparateDatabaseAndState
Teraz, gdy wiesz już o stanie projektu, który buduje Django, nadszedł czas, aby przyjrzeć się operacji SeparateDatabaseAndState
. Ta operacja może zrobić dokładnie to, co sugeruje nazwa:może oddzielić stan projektu (model mentalny budowany przez Django) od bazy danych.
SeparateDatabaseAndState
jest tworzony z dwiema listami operacji:
state_operations
zawiera operacje, które są stosowane tylko do stanu projektu.database_operations
zawiera operacje, które są stosowane tylko do bazy danych.
Ta operacja pozwala wprowadzić dowolne zmiany w bazie danych, ale Twoim obowiązkiem jest upewnienie się, że stan projektu będzie później pasował do bazy danych. Przykładowe przypadki użycia dla SeparateDatabaseAndState
przenosisz model z jednej aplikacji do drugiej lub tworzysz indeks w ogromnej bazie danych bez przestojów.
SeparateDatabaseAndState
to zaawansowana operacja i nie będziesz potrzebować pierwszego dnia pracy z migracjami, a może wcale. SeparateDatabaseAndState
jest podobny do operacji serca. Wiąże się to z pewnym ryzykiem i nie jest czymś, co robi się tylko dla zabawy, ale czasami jest to niezbędna procedura, aby utrzymać pacjenta przy życiu.
Wniosek
To kończy twoje głębokie zanurzenie w migracjach Django. Gratulacje! Omówiłeś sporo zaawansowanych tematów i teraz dobrze rozumiesz, co dzieje się pod maską migracji.
Dowiedziałeś się, że:
- Django śledzi zastosowane migracje w tabeli migracji Django.
- Migracje Django składają się ze zwykłych plików Pythona zawierających element
Migration
klasa. - Django wie, jakie zmiany wykonać z
operations
lista wMigration
zajęcia. - Django porównuje Twoje modele ze stanem projektu, który buduje na podstawie migracji.
Mając tę wiedzę, jesteś gotowy, aby zająć się trzecią częścią serii o migracjach Django, w której dowiesz się, jak używać migracji danych, aby bezpiecznie wprowadzać jednorazowe zmiany w swoich danych. Bądź na bieżąco!
W tym artykule wykorzystano bitcoin_tracker
Projekt Django zbudowany w Django Migrations:A Primer. Możesz albo odtworzyć ten projekt, pracując nad tym artykułem, albo pobrać kod źródłowy:
Pobierz kod źródłowy: Kliknij tutaj, aby pobrać kod projektu migracji Django, którego będziesz używać w tym artykule.