Як зробити міграцію Django — поради експерта NIX

20 Лютого 2023

Під час роботи з фреймворком Django чимало розробників синхронізує код моделей із базою даних у застосунку за допомогою Django Migrations. Цей механізм зручний, адже все проходить автоматично. Але в реальності міграції Django можуть приносити й проблеми. У своїй статті Backend Developer Михайло Сердюк розповідає, як їх вирішити.

Що таке Django Migrate

 

Почнемо з теорії. Уявімо, що ви вказали певний клас в файлі моделей в застосунку на Django. Цей клас описує, якою повинна бути таблиця із даними.

Міграція баз даних у Django

Тоді із міграцією не було б проблем. Але вони можуть виникнути, якщо з’явиться потреба в доповненні моделі. Припустимо, потрібно в Question додати поле з описом title питання. Здається, що можна вписати його, але цього не вистачить. Аби система занесла зміни до БД, модель необхідно промігрувати до Django. Трохи нижче я поясню, чому на наступному прикладі я вписав для поля title параметр blank=True.

Міграція баз даних у Django

Тож після зміни БД потрібно створити Django Migration. Для цього потрібна команда manage.py makemigrations. Далі Django самостійно створить в хронологічному порядку, за нумерацією, файли із міграціями. При першому запуску застосунку з’явиться initial-міграція, за нею все наступні. В цьому випадку це друга міграція зі змінами, які вказані в моделі.

Міграція баз даних у Django

Проте тут з’явилися виключено ті файли, які порівняли стан файлу із моделями в застосунку. Тобто Django провів аналіз файлів створених міграцій із класами та моделями з файлу model.py. І основою для файлу міграції стала різниця між ними. Проте для застосування подібного файлу в БД слід запустити команду manage.py migrate. В результаті в консолі ми побачимо, що міграції застосовані. Після першого старту вивід буде великим. Адже в системі чимало вбудованих моделей, які застосовуються під час Django міграції в першу чергу.

Міграція баз даних у Django

Файл із міграцією вартий окремої розповіді. Передусім в ньому вказано залежності, з яких його створено. Для цього формується лінк із БД на останню застосовану міграцію. Також в файлі вказано необхідні зміні щодо БД. В прикладі це створення в question поля з назвою title, а також його параметри:

Міграція баз даних у Django

Навіщо розбирати Django Migrations

 

Хтось може подумати: відповідь на питання, як зробити міграцію Django, проста. Ніби треба працювати лише із командами makemigrations і migration. В маленьких проєктах це має сенс: там достатньо написати та застосувати міграцію, а потім додати код для оперування даними. Але задача може формулюватися абстрактно. Уявімо клієнта, який хочу отримати транспорт для перевезення вантажу із точки А в точку Б. Якщо йти шляхом найменшого спротиву, то можна зробити велосипед. Адже це транспорт, на якому можна перевезти вантаж… У випадку розробників такий абстрактний підхід може призвести до БД зі схемою, як на ілюстрації:

Міграція баз даних у Django

Це реальний приклад першого варіанту бази даних. В ньому були величезні моделі із властивостями, які пов’язані з основними моделями. Проте у клієнта були зауваження. Якщо використати аналогію із велосипедом, коментарі виявилися приблизно такими. Насамперед потрібен швидший за велосипед транспорт, бо відстань між точками А та Б велика. Дорога йде схилами та підйомами, тому двигун підійде краще, аніж педалі. А ще транспорт буде використовуватися й взимку, тож потрібна кабіна з обігрівачем. Та й велосипедного багажника для вантажів замало… В результаті уточнень структура БД перетворюється на принципово інший концепт. У нас схема трансформувалась в наступну ілюстрацію (хоча й це не фінальний варіант):

Міграція баз даних у Django

Це доводить: регулярні модифікації баз даних є нормальним процесом. Неможливо від початку прорахувати усі ризики, завдання може корегуватися. Та й клієнти часто не можуть детально описати потрібний продукт. Тому Django міграція стане у нагоді будь-якому розробнику. Вона допоможе виправляти схему БД та відкочувати зміни. Це трапляється у багатьох випадках, та є дієві варіанти подолання проблем.

Як замінити непотрібну міграцію

 

Найпростіший сценарій: створення файлу без запуску команди в Django migrate. В цьому випадку зміни в БД не застосовані. А тому можна видалити останні файли з міграціями, змінити моделі, створити та застосувати нову міграцію.

Більш складний варіант: із запуском migrate та застосуванням змін із міграціями до БД. Але треба розібратися, чи взагалі має цінність база даних. Якщо вона не потрібна, то видаляйте міграції та саму БД, а потім створюйте міграції зі змінами та мігруйте в нову базу. Якщо ж БД має цінність, тоді відкочуйтеся до попередніх міграцій за допомогою команди manage.py show migration.

Далі ви побачите міграції Django, які було застосовано в БД. В прикладі попередньою була база 0010_previous_migration. Тому для повернення треба прописати команду manage.py migrate my_app 0010_previous_migration.

Далі вказуємо назви застосунку та потрібної міграції. Після відкочування залишиться позбутися зайвої міграції, зробити зміни в моделях та знову промігрувати. Також можна повернутися й до стартової міграції, яку зробили на початку створення застосунку. Тут допоможе команда manage.py migrate my_app zero.

Нестандартні сценарії Django міграції

 

Більшість розробників стикалися із описаними прикладами. Але інколи бувають більш складні сценарії. Розберемо поширені проблеми.

Міграція баз даних у Django

Припустимо, вам необхідно в моделі question із полем question_text додати title з указанням довжини даних в цьому полі. При створенні Django Migration система скаже, що у такому полі не зазначено значення по дефолту. При цьому повідомлення буде повторюватися, навіть якщо для нових полів таке поле буде заповнюватись.

Міграція баз даних у Django

Цю проблему можна вирішити в різні способи.

  1. Вказати значення за замовчуванням

Наступна ілюстрація розкриває суть проблеми: у нас створено поле title, яке в попередніх записах у БД не заповнено.

Міграція баз даних у Django

Ви можете заповнювати раніше створені поля одноразовими значеннями. Для цього необхідно прописати в консолі значення для заповнення таблиці за замовчуванням. В прикладі це буде default title.

Міграція баз даних у Django

Завдяки цьому таблиця з попередніми записами буде повністю заповненою. Але ви можете використати в моделі ще й багаторазові значення.

Міграція баз даних у Django

Для цього треба додати параметр default і в ньому описати значення нових та попередніх значень. А коли Django міграція буде запущена, створиться нове поле, в якому будуть заповнені всі попередні записи.

Міграція баз даних у Django

  1. Залишити поле взагалі пустим

Інший варіант, як зробити міграцію Django успішною, не потребує заповнення поля. У прикладі я додав поле з датою published_at, і йому дозволено бути порожнім. На це вказують параметри null=True і blank=True.

Міграція баз даних у Django

Під час створення в таблиці з question запису є обов’язкові поля (як question_text) та необов’язкові (тут це published_at). Останні завдяки маркеру null=True будуть заповнені значенням null, вони залишаться порожніми. І заповнити їх можна пізніше.

Міграція баз даних у Django

А blank=True відповідає за роботу в адмінці Django. Адміністративна панель навіть із null=True при створенні запису не дасть зробити порожні записи. І за допомогою параметру blank=True це стане можливим.

  1. Без позначення default чи null

Інколи при проведенні Django Migrations недоцільно або неможливо залишати поля пустими, що виглядає як нелогічне поводження моделей. Уявімо дві моделі: Question для опису питання та Choice для набору відповідей на питання.

Міграція баз даних у Django

А далі знадобиться об’єднати квесчени та чойси.

Міграція баз даних у Django

Насамперед треба вказати, до якого питання відноситься question. Тому неможливо залишити поле порожнім або заповненим за замовчуванням. Сам Django в цьому разі вказуватиме на помилку і не розумітиме, як працювати із записами без дефолту.

Простіше за все видалити базу даних та старі Django міграції, створити БД, змінити модель і стартувати нову міграцію. Вона побудує зв’язки, тому заповнення бази надалі буде коректним. Але якщо дані в БД важливі для вас, такий варіант не підійде. Але допоможе дублікатна модель для Choice:

Міграція баз даних у Django

У мене в прикладі це QuestionChoice, яка складається із choice_text і question разом із ForeignKey-зв’язком на Question. Після цього треба зробити Django Migrate, мігрувати нову модель до БД та створити дата-міграцію. З її допомогою модель QuestionChoice заповнюватиметься даними за відомою логікою: з моделі Choice та зав’язаними на кожен запис ключами на запис в моделі Question. Далі треба запустити дата-міграцію та використати зміни. І далі залишиться видалити першу модель Choice, зробити міграцію та перейменувати дублікат QuestionChoice на базовий Choice.

Що таке дата-міграції

 

Все, що було вище — це структурні міграції Django. Вони відповідають за зміни в структурі даних. Але в останньому прикладі з’явилося таке поняття, як дата-міграції. Цей різновид допомагає промігрувати та зберегти дані за незрозумілими для самого Django правилами. Для старту дата-міграцій спочатку необхідно створити порожню міграцію командою manage.py makemigrations –empty yourappname. Так з’явиться міграція, як на наступній ілюстрації:

Міграція баз даних у Django

Далі треба в середині пустої Django Migration прописати певні дані для дата-міграцій. Передусім це функція із логікою, яку система повинна виконати.

Міграція баз даних у Django

В прикладі виникла потреба в створенні й заповненні поля name. Воно має включати first_name та last_name, які були в попередній моделі. В такому разі просто створюємо функцію, викликаємо її в операторі operations, а далі стартуємо Django міграцію. І система зробить все за нас!

Як скасувати дата-міграцію

 

Інколи треба відкотити готову дата-міграцію. Але при використанні описаного механізму ми побачимо повідомлення, як на наступній ілюстрації:

Міграція баз даних у Django

Це викликано тим, що при автоматичному створенні міграції всередині функції міграції з’являється дзеркальна міграція відкату. Тобто буде дві міграції: та, що мігрує, і та, що повертає значення. При самостійному описі дата-міграції девелопери часто нехтують створенням зворотної міграції (адже треба писати додатковий код), що й викликає труднощі. Тому мало знати, як зробити міграцію в Django. Треба ще й зрозуміти, як прописати функцію відкату, якщо ви плануєте часто відкочувати дата-міграцію. Задля цього її треба вказати другим параметром у функції RunPython усередині operations:

Міграція баз даних у Django

Як зробити перезапуск дата-міграцій

 

Створювати дата-міграції можна із різною логікою. Наприклад, ви можете робити такі Django Migrations із планом на кілька стартів та додаткове заповнення. Але це може бути неможливим. Також може взагалі не бути бажання прописувати функцію відкату. Тоді вам потрібна фейк-міграція:

Міграція баз даних у Django

Маркер fake допоможе відкотити дата-міграцію та знов стартувати її. Припустимо, ми мігрували до восьмої міграції Django, а потім завдяки фейковій міграції повернулися до сьомої.

Міграція баз даних у Django

А після відкату з оператором fake ми повернулися до сьомої міграції, змінили щось у восьмій та перезапустили міграцію. Так все пройде без проблем:

Міграція баз даних у Django

Що таке фейк-міграції

 

Поняття фейкової Django міграції заслуговує на окрему розповідь. Міграція має два стани. Перший є суттєвим. Ви можете побачити його в папці із файлами migrate. А другий стан є записом в БД. Там є таблиця з усіма застосованими до бази міграціями. При створенні фейк-міграції вона в цій таблиці додає або видаляє запис, проте сама така міграція не застосовується в жоден бік. Тож при роботі із дата-міграціями зручно робити назад або вперед фейкові міграції. Завдяки цьому можна додати запис у таблицю з Django Migrate, але зміни із БД не виконувати.

Із фейк-міграціями працюють ті ж тулзи, що зі звичайними. Ви можете повертатися до початкових міграцій, робити фейкові, відкочуватися до потрібної версії, видаляти записи з таблиці та не застосовувати зміни БД. Все це можливо із командами:

manage.py migrate –fake-initial

manage.py migrate –fake myapp

manage.py migrate –fake myapp migration_name

Та хоча фейк-міграції ефективно працюють із дата-міграціями, робота зі структурними міграціями є ризикованою. Припустимо, ви хочете видалити запис міграції під час створення певної моделі. В такому випадку в таблиці будуть відсутні дані про виконання міграції зі створенням такої моделі, хоча в БД модель буде створена. І коли буде повторна Django міграція таблиці, може з’явитися помилка, яку викликала спроба створення наявного у БД поля.

Що таке Squashing migrations

 

У довготривалих проєктах може назбиратися кілька сотень файлів із міграціями. Django нормально працює із такої структурою, але це незручно для розробника. Припустимо, вам треба відкотитися до сотої міграції зі двохсот і потім повернути назад. Моніторити це складно, тому в об’єднанні та оптимізації допоможе Squashing Django Migration.

Використання такого інструменту дозволяє в автоматичному режимі оптимізувати всі міграції в застосунку або окремо вказувати, до якої міграції потрібен сквошинг. В цьому разі система проаналізує всі міграції, які було виконано після вказаної міграції Django. На прикладі це четверта версія. Далі буде виконана оптимізація та перевірка, чи не суперечать виконані дії одна одній. А після цього програмі залишиться скомпонувати все в єдиний файл та створити сквош-міграцію:

Міграція баз даних у Django

А далі треба зафіксувати Squashing міграцію, для чого необхідно:

  • Видалити міграційні файли, які включено до сквош-міграції.
  • Оновити міграції, які мали залежності від видалених міграцій.
  • Змінити атрибут Migration в класі сквош-міграції. Це покаже системі, що далі треба орієнтуватися саме на цю стиснену міграцію.

Тож розібратися, як зробити міграцію Django такого чи іншого типу, досвідченому розробнику не складно. Головне — пам’ятайте про перелічені ризики.