Това е втората статия от нашата серия за миграции на Django:
- Част 1:Django Migrations:A Primer
- Част 2:Копаене по-дълбоко в миграциите на Django (текуща статия)
- Част 3:Миграции на данни
- Видео:Django 1.7 Migrations – A Primer
В предишната статия от тази серия научихте за целта на миграциите на Django. Запознали сте се с основните модели на използване като създаване и прилагане на миграции. Сега е време да копаем по-дълбоко в системата за миграция и да надникнем в някои от основните й механизми.
До края на тази статия ще знаете:
- Как Django следи миграциите
- Как миграциите знаят кои операции с база данни да извършат
- Как се дефинират зависимостите между миграциите
След като се запознаете с тази част от системата за миграция на Django, ще сте добре подготвени да създадете свои собствени персонализирани миграции. Нека скочим точно там, където спряхме!
Тази статия използва bitcoin_tracker
Проект Django, изграден в Django Migrations:A Primer. Можете или да създадете отново този проект, като работите през тази статия, или можете да изтеглите изходния код:
Изтеглете изходния код: Щракнете тук, за да изтеглите кода за проекта за миграции на Django, който ще използвате в тази статия.
Как Django знае кои миграции да приложи
Нека обобщим последната стъпка от предишната статия от поредицата. Създадохте миграция и след това приложихте всички налични миграции с python manage.py migrate
.Ако тази команда се изпълни успешно, тогава таблиците на вашата база данни вече съответстват на дефинициите на вашия модел.
Какво ще стане, ако изпълните тази команда отново? Нека го изпробваме:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Нищо не се е случило! След като се приложи миграция към база данни, Django няма да приложи отново тази миграция към тази конкретна база данни. За да се гарантира, че миграцията се прилага само веднъж, изисква проследяване на приложените миграции.
Django използва таблица на база данни, наречена django_migrations
. Django автоматично създава тази таблица във вашата база данни при първото прилагане на миграции. За всяка миграция, която е приложена или фалшифицирана, в таблицата се вмъква нов ред.
Например, ето как изглежда тази таблица в нашия bitcoin_tracker
проект:
ID | Приложение | Име | Приложено |
---|---|---|---|
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 |
Както можете да видите, има запис за всяка приложена миграция. Таблицата не само съдържа миграциите от нашите historical_data
приложение, но също и миграциите от всички други инсталирани приложения.
Следващият път, когато миграциите се изпълняват, Django ще пропусне миграциите, изброени в таблицата на базата данни. Това означава, че дори ако промените ръчно файла на вече приложена миграция, Django ще игнорира тези промени, стига вече да има запис за него в базата данни.
Можете да подмамите Django да изпълни повторно миграция, като изтриете съответния ред от таблицата, но това рядко е добра идея и може да ви остави с повредена система за миграция.
Файлът за миграция
Какво се случва, когато стартирате python manage.py makemigrations <appname>
? Django търси промени, направени в моделите във вашето приложение <appname>
. Ако намери такъв, като модел, който е добавен, тогава създава файл за миграция в migrations
поддиректория. Този файл за мигриране съдържа списък с операции за синхронизиране на вашата схема на база данни с дефиницията на вашия модел.
Забележка: Приложението ви трябва да бъде посочено в INSTALLED_APPS
настройка и трябва да съдържа migrations
директория с __init__.py
файл. В противен случай Django няма да създаде никакви миграции за него.
migrations
директорията се създава автоматично, когато създадете ново приложение с startapp
команда за управление, но е лесно да се забрави, когато създавате приложение ръчно.
Файловете за миграция са просто Python, така че нека да разгледаме първия файл за миграция в historical_prices
ап. Можете да го намерите на historical_prices/migrations/0001_initial.py
. Трябва да изглежда така:
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,),
),
]
Както можете да видите, той съдържа един клас, наречен Migration
който наследява от django.db.migrations.Migration
. Това е класът, който рамката за мигриране ще търси и изпълнява, когато го помолите да приложи миграции.
Migration
клас съдържа два основни списъка:
dependencies
operations
Операции по миграция
Нека разгледаме operations
списък първо. Тази таблица съдържа операциите, които трябва да се извършат като част от миграцията. Операциите са подкласове на класа django.db.migrations.operations.base.Operation
. Ето общите операции, които са вградени в Django:
Операционен клас | Описание |
---|---|
CreateModel | Създава нов модел и съответната таблица на базата данни |
DeleteModel | Изтрива модел и изтрива таблицата на базата данни |
RenameModel | Преименува модел и преименува неговата таблица на база данни |
AlterModelTable | Преименува таблицата на базата данни за модел |
AlterUniqueTogether | Променя уникалните ограничения на модел |
AlterIndexTogether | Променя индексите на модел |
AlterOrderWithRespectTo | Създава или изтрива _order колона за модел |
AlterModelOptions | Променя различни опции на модела, без да засяга базата данни |
AlterModelManagers | Променя наличните мениджъри по време на миграции |
AddField | Добавя поле към модел и съответната колона в базата данни |
RemoveField | Премахва поле от модел и премахва съответната колона от базата данни |
AlterField | Променя дефиницията на поле и променя колоната на базата данни, ако е необходимо |
RenameField | Преименува поле и, ако е необходимо, неговата колона от база данни |
AddIndex | Създава индекс в таблицата на базата данни за модела |
RemoveIndex | Премахва индекс от таблицата на базата данни за модела |
Обърнете внимание как операциите са наименувани след промени, направени в дефинициите на модела, а не действията, които се извършват в базата данни. Когато приложите миграция, всяка операция е отговорна за генерирането на необходимите SQL изрази за вашата конкретна база данни. Например, CreateModel
ще генерира CREATE TABLE
SQL израз.
Извън кутията миграциите имат поддръжка за всички стандартни бази данни, които Django поддържа. Така че, ако се придържате към операциите, изброени тук, тогава можете да правите повече или по-малко всякакви промени във вашите модели, които искате, без да се притеснявате за основния SQL. Всичко е направено за вас.
Забележка: В някои случаи Django може да не открие правилно промените ви. Ако преименувате модел и промените няколко от полетата му, Django може да сбърка това с нов модел.
Вместо RenameModel
и няколко AlterField
операции, той ще създаде DeleteModel
и CreateModel
операция. Вместо да преименува таблицата на базата данни за модела, тя ще я изпусне и ще създаде нова таблица с новото име, като ефективно ще изтрие всичките ви данни!
Създайте си навик да проверявате генерираните миграции и да ги тествате върху копие на вашата база данни, преди да ги стартирате върху производствени данни.
Django предоставя още три операционни класа за разширени случаи на употреба:
RunSQL
ви позволява да изпълнявате персонализиран SQL в базата данни.RunPython
ви позволява да изпълнявате всеки код на Python.SeparateDatabaseAndState
е специализирана операция за напреднали приложения.
С тези операции можете основно да правите всякакви промени, които искате във вашата база данни. Въпреки това, няма да намерите тези операции в миграция, която е създадена автоматично с makemigrations
команда за управление.
След Django 2.0 има и няколко специфични за PostgreSQL операции, налични в django.contrib.postgres.operations
които можете да използвате за инсталиране на различни разширения на PostgreSQL:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Имайте предвид, че миграция, съдържаща една от тези операции, изисква потребител на база данни с привилегии на суперпотребител.
Не на последно място, можете също да създадете свои собствени операционни класове. Ако искате да разгледате това, тогава разгледайте документацията на Django за създаване на персонализирани операции за миграция.
Зависимости от миграция
dependencies
списъкът в клас за мигриране съдържа всички миграции, които трябва да бъдат приложени, преди да може да се приложи тази миграция.
В 0001_initial.py
миграция, която видяхте по-горе, нищо не трябва да се прилага предварително, така че да няма зависимости. Нека да разгледаме втората миграция в historical_prices
ап. Във файла 0002_switch_to_decimals.py
, dependencies
атрибут на Migration
има запис:
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),
),
]
Зависимостта по-горе казва, че миграцията 0001_initial
на приложението historical_data
първо трябва да се стартира. Това има смисъл, тъй като миграцията 0001_initial
създава таблицата, съдържаща полето, което миграцията 0002_switch_to_decimals
иска да се промени.
Една миграция може също да има зависимост от миграция от друго приложение, като това:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Това обикновено е необходимо, ако моделът има външен ключ, насочващ към модел в друго приложение.
Като алтернатива можете също да наложите миграцията да се изпълнява преди друга миграция с помощта на атрибута run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Зависимостите също могат да бъдат комбинирани, така че да имате множество зависимости. Тази функционалност осигурява голяма гъвкавост, тъй като можете да поставите външни ключове, които зависят от модели от различни приложения.
Опцията за изрично дефиниране на зависимости между миграциите също означава, че номерирането на миграциите (обикновено 0001
, 0002
, 0003
, ...) не представлява строго реда, в който се прилагат миграциите. Можете да добавите всяка зависимост, която искате, и по този начин да контролирате реда, без да се налага да преномерирате всички миграции.
Преглед на миграцията
По принцип не е нужно да се притеснявате за SQL, който генерират миграциите. Но ако искате да проверите отново дали генерираният SQL има смисъл или просто сте любопитни как изглежда, тогава Django ви е покрил с sqlmigrate
команда за управление:
$ 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;
Това ще изброи основните SQL заявки, които ще бъдат генерирани от посочената миграция въз основа на базата данни във вашия settings.py
файл. Когато предадете параметъра --backwards
, Django генерира SQL, за да отмени миграцията:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
След като видите изхода на sqlmigrate
за малко по-сложна миграция може да оцените, че не е нужно да изработвате целия този SQL на ръка!
Как Django открива промени във вашите модели
Видяхте как изглежда файлът за миграция и как е неговият списък с Operation
classes дефинира извършените промени в базата данни. Но как точно Django знае кои операции трябва да влязат във файл за миграция? Може да очаквате, че Django сравнява вашите модели със схемата на вашата база данни, но това не е така.
Когато изпълнявате makemigrations
, Django не проверете вашата база данни. Нито сравнява файла на вашия модел с по-ранна версия. Вместо това Django преминава през всички миграции, които са били приложени, и изгражда състояние на проекта за това как трябва да изглеждат моделите. След това това състояние на проекта се сравнява с текущите дефиниции на модела и се създава списък с операции, който, когато се приложи, ще актуализира състоянието на проекта с дефинициите на модела.
Игра на шах с Django
Можете да мислите за моделите си като за шахматна дъска, а Джанго е гросмайстор на шах, който ви наблюдава как играете срещу себе си. Но гросмайсторът не следи всяко твое движение. Грандмайсторът поглежда към дъската само когато крещиш makemigrations
.
Тъй като има само ограничен набор от възможни ходове (а гросмайсторът е гросмайстор), тя може да измисли ходовете, които са се случили, откакто за последно е погледнала дъската. Тя си прави бележки и ви позволява да играете, докато не извикате makemigrations
отново.
Когато гледа дъската следващия път, гросмайсторът не си спомня как е изглеждала шахматната дъска последния път, но може да прегледа бележките си за предишните ходове и да изгради мислен модел на това как е изглеждала шахматната дъска.
Сега, когато извикате migrate
, гросмайсторът ще изиграе отново всички записани ходове на друга шахматна дъска и ще отбележи в електронна таблица кои от нейните записи вече са приложени. Тази втора шахматна дъска е вашата база данни, а електронната таблица е django_migrations
таблица.
Тази аналогия е доста подходяща, защото илюстрира добре някои поведения на Django миграции:
-
Джанго миграциите се опитват да бъдат ефективни: Точно както гросмайсторът предполага, че сте направили най-малък брой ходове, Django ще се опита да създаде най-ефективните миграции. Ако добавите поле с име
A
към модел, след което го преименувайте наB
, и след това стартирайтеmakemigrations
, тогава Django ще създаде нова миграция, за да добави поле с имеB
. -
Джанго миграциите имат своите ограничения: Ако направите много ходове, преди да оставите гросмайстора да погледне шахматната дъска, тогава тя може да не е в състояние да проследи точните движения на всяка фигура. По същия начин Django може да не измисли правилната миграция, ако направите твърде много промени наведнъж.
-
Django migration очаква да играете по правилата: Когато направите нещо неочаквано, като например да вземете произволна фигура от дъската или да бъркате с нотите, гросмайсторът може да не забележи в началото, но рано или късно тя ще вдигне ръце и ще откаже да продължи. Същото се случва, когато се забърквате с
django_migrations
таблица или променете вашата схема на база данни извън миграцията, например като изтриете таблицата на базата данни за модел.
Разбиране на SeparateDatabaseAndState
Сега, когато знаете за състоянието на проекта, което Django изгражда, е време да разгледаме по-отблизо операцията SeparateDatabaseAndState
. Тази операция може да направи точно това, което подсказва името:може да отдели състоянието на проекта (менталният модел, който Django изгражда) от вашата база данни.
SeparateDatabaseAndState
се инстанцира с два списъка с операции:
state_operations
съдържа операции, които се прилагат само към състоянието на проекта.database_operations
съдържа операции, които се прилагат само към базата данни.
Тази операция ви позволява да правите всякакъв вид промяна във вашата база данни, но ваша отговорност е да се уверите, че състоянието на проекта отговаря на базата данни след това. Примерни случаи на употреба за SeparateDatabaseAndState
премествате модел от едно приложение в друго или създавате индекс в огромна база данни без прекъсвания.
SeparateDatabaseAndState
е усъвършенствана операция и няма да имате нужда от работа с миграции през първия си ден, а може би изобщо никога. SeparateDatabaseAndState
е подобно на сърдечната хирургия. Това носи доста голям риск и не е нещо, което правите просто за забавление, но понякога е необходима процедура, за да поддържате пациента жив.
Заключение
Това завършва вашето дълбоко потапяне в миграциите на Django. Честито! Покрихте доста напреднали теми и вече имате солидно разбиране какво се случва под капака на миграцията.
Научихте, че:
- Django следи приложените миграции в таблицата за миграции на Django.
- Джанго миграциите се състоят от обикновени Python файлове, съдържащи
Migration
клас. - Django знае кои промени да извърши от
operations
списък вMigration
класове. - Django сравнява вашите модели със състояние на проекта, което изгражда от миграциите.
С тези знания вече сте готови да се заемете с третата част от поредицата за миграциите на Django, където ще научите как да използвате миграциите на данни, за да правите безопасно еднократни промени в данните си. Останете на линия!
Тази статия използва bitcoin_tracker
Проект Django, изграден в Django Migrations:A Primer. Можете или да създадете отново този проект, като работите през тази статия, или можете да изтеглите изходния код:
Изтеглете изходния код: Щракнете тук, за да изтеглите кода за проекта за миграции на Django, който ще използвате в тази статия.