UA RU
logo
Back-end      17/06/2022

Все не так просто, як здається: розбираємо складні випадки міграції баз даних у Django

Михайло Сердюк BLOG

Backend Developer в NIX

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

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

Зміст

1. Що таке міграції
2. Навіщо розбиратися з міграціями
3. Як замінити міграцію
4. Складні випадки при міграціях
5. Дата-міграції
6. Скасування дата-міграції
7. Повторний запуск дата-міграцій
8. Фейк-міграції
9. Squashing migrations
10. Замість висновку

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

Для початку — трохи теорії. Припустимо, що в Django-застосунку в файлі моделей ми вказали певний клас, щоб описати, як має виглядати наша таблиця з даними:

 

У такому випадку зі створенням міграцій все було б добре. Але уявімо, що в якийсь момент з’явилась необхідність доповнити модель.

Наприклад, в Question треба додати поле для опису title питання. Може здатися, що достатньо просто вписати його, але насправді цього замало. Щоб Django сприйняв ці зміни і заніс їх до бази даних, модель потрібно промігрувати до Django. У наведеному нижче прикладі я навмисно задав для поля title параметр blank=True. Трохи далі по тексту поясню це:

 

UI/UX designer від Mate academy.
UI/UX designer досліджуєте, що турбує користувача та створює візуальну частину додатку чи сайту. Станьте таким спеціалістом після нашого курсу! .
Отримати знижку на курс

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

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

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

 

Варто розібратися, що являє собою сам файл із міграцією:

  • По-перше, в ньому вказуються залежності, з яких він створений. Для цього робиться посилання з бази даних на останню застосовану міграцію.
  • По-друге, в файлі вказуються потрібні зміни в базі даних. У нашому випадку це створення в моделі question нового поля з назвою title і параметри для нього:
  • Основи Web дизайну від Hillel IT School.
    Цей онлайн-курс з основ веб-дизайну дозволить вам опанувати мистецтво створення ефективних та привабливих інтерфейсів для вебсайтів і застосунків. Ви оволодієте ключовими принципами UX/UI дизайну, створюватимете дизайн-макети та прототипи, розроблятимете адаптивні інтерфейси для різних пристроїв, готуючись до професійної кар'єри в галузі веб-дизайну.
    Дізнатися більше

Навіщо розбиратися з міграціями

Може здатися, що тут достатньо оперувати лише двома командами: makemigrations та migration. На невеликих проєктах це саме так і відбувається: пишете та застосовуєте міграції і додаєте код для оперування даними. Однак часто замовник формує задачу дуже абстрактно.

Поясню через аналогію. Клієнту потрібен транспорт, на якому він зможе дістатися з пункту А до пункту Б і перевезти вантаж. Виконавець, як раціональна людина, піде шляхом найменшого спротиву та зробить велосипед. Це транспорт? Так. Ним можна дістатися з пункту А до пункту Б? Можна. Він підходить для перевезення вантажу? Безумовно. І якщо повернутися до наших з вами завдань, то подібна ситуація може призвести до схеми бази даних, яка наведена на цій ілюстрації:

Це приклад першої реалізації бази даних, взятий з реальної практики. Тут були великі моделі, у них були властивості, пов’язані з основними моделями. Але замовник сказав, що має декілька зауважень.

Якщо повернутися до нашої аналогії, ситуація виглядала приблизно так:

  • по-перше, відстань від пункту А до пункту Б велика, тому потрібен більш швидкий транспортний засіб, аніж велосипед;
  • по-друге, на шляху чимало підйомів та спусків, тому замість педалей краще встановити двигун;
  • по-третє, використання транспорту планується впродовж року, тому потрібна кабіна з обігрівачем і кондиціонером;
  • Англійська для початківців від Englishdom.
    Для тих, хто тільки починає вивчати англійську і хоче вміти використовувати базову лексику і граматику.
    Реєстрація на курс
  • та й взагалі вантажі великі, і багажника на велосипеді замало.

Після зауважень розробник вносить зміни до структури бази даних, і згодом проєкт трансформується у дещо інше. Результат змін ви можете побачити на наступній ілюстрації. До речі, ця схема — ще не кінцевий варіант, а десь 10% від реальної структури:

Цей приклад каже нам: постійні зміни бази даних — це нормально. Бо відразу прорахувати можливі ризики складно. Задача може коригуватися декілька разів. Та й, відверто кажучи, інколи сам замовник не може чітко описати потрібний йому продукт.

Саме тому розробнику дуже часто потрібно виправляти певні моменти в схемі бази даних. Для цього він має відкотити у змінах міграції все назад.

Таке може статися в декількох випадках. На щастя, є дієві варіанти, як із цим впоратися.

Як замінити міграцію

Перший і найбільш простий сценарій — коли ми створили файл, але не запустили команду migrate. В такому разі зміни до бази даних не були застосовані. Тому можна просто видалити останні файли з міграціями, зробити зміни в моделях, створити нову міграцію та застосувати її.

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

Далі побачимо застосовані в базу даних міграції. В моєму випадку попередньою була 0010_previous_migration. Щоб до неї відкотитися, ми прописуємо команду:

Практичний інтенсивний курс з дизайну - Design Booster від Powercode academy.
Навчіться дизайну з нуля за 3 місяці і заробляйте перші $1000, навіть якщо ви не маєте креативного мислення, смаку або вміння малювати. Отримайте практичні навички, необхідні для успішної кар'єри в дизайні.
Зарееструватися

manage.py migrate my_app 0010_previous_migration

Зазначаємо тут назви нашого застосунку і бажаної міграції. Після відкату залишається видалити непотрібну міграцію та зробити зміни в моделях, а потім заново промігрувати.

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

Складні випадки при міграціях

Впевнений, що більшість із вас з описаними прикладами вже так чи інакше стикалися. Але що робити в більш складних ситуаціях? Спробуємо розібрати найбільш поширені варіанти подібних проблем:

 

Уявімо, що в моделі question із полем question_text необхідно додати title з описом, якої довжини мають бути дані в цьому полі.

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

 

Онлайн-курс Frontend-разробник від Powercode academy.
Курс на якому ти напишеш свій чистий код на JavaScript, попрацюєш із різними видами верстки, а також адаптаціями проектів під будь-які екрани. .
Зарееструватися

Є декілька шляхів вирішення цієї проблеми. Розглянемо їх.

  • Задати значення за замовчуванням

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

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

 

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

 

Онлайн-курс повного дня Front-end developer від Mate academy.
Цей курс ідеальний для новачків - 85% наших студентів не мали попереднього досвіду. Гарантованне працевлаштування: 3 500 випускників вже отримало роботу. .
Отримати знижку на курс

Додаємо параметр default і в ньому описуємо значення для попередніх та нових записів. Після запуску міграції буде створено нове поле — і попередні записи в ньому будуть вже заповнені.

 

  • Залишити поле порожнім

На цьому прикладі я додав поле з датою published_at, якому дозволено бути пустим. Для цього потрібно вказати два параметри: null=True та blank=True.

 

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

 

Онлайн курс UI/UX Design Pro від Hillel IT School.
Навчіться проєктувати інтерфейси з урахуванням поведінки користувачів, розв'язувати їх проблеми через Customer Journey Mapping, створювати дизайн-системи і проводити дослідження юзабіліті, включаючи проєктування мобільних додатків для Android та iOS і розробку UX/UI на основі даних!
Дізнатися більше

 

Також є параметр blank=True. Якщо null відповідає за поводження у середині бази даних, то blank — за роботу в адмінці Django. Навіть за наявності поля null=True при створенні запису адміністративна панель не дозволить зробити пусті записи без поля blank=True. За допомогою цієї позначки ми можемо створювати в адмінці такі поля.

  • Якщо не хочеться ставити default або null

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

В якийсь момент вам потрібно об’єднати чойси з квесченами.

Для цього вказуємо, до якого запитання відноситься кожен question. Через це ми не можемо дозволити, щоб поле було пустим або заповненим по дефолту. Навіть Django буде казати, щось не так. Адже він розумітиме, як поводитися з попередніми записами без дефолту.

Найпростіший спосіб — знести базу даних, видалити старі міграції, створити нову базу, внести необхідні зміни в моделі та створити міграцію. Вона задасть ці зв’язки, і кожного разу при заповненні бази даних все буде добре. Якщо ж дані в цій базі дуже важливі, то цей спосіб не підходить. Тоді варто створити дублікатну модель для моделі Choice:

Онлайн-курс DevOps engineer від Mate academy.
DevOps інженери відповідають за автоматизацію процесів розробки, тестування та випуску продукту. Завдяки цьому курсу ви швидко станете високооплачуваним спеціалістом.
Отримати знижку на курс

 

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

Дата-міграції

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

manage.py makemigrations --empty yourappname

У результаті ми отримуємо міграцію у такому вигляді:

 

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

Курс Розмовної англійської від Englishdom.
Після цього курсу ви зможете спілкуватись з іноземцями і цікаво розкажете про себе.
Приєднатися

 

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

Скасування дата-міграції

Іноді потрібно скасувати чи відкотити створену дата-міграцію. Проте якщо використати вже описаний вище механізм, з’явиться наступний warning:

 

Причина цього — при створенні міграції в автоматичному режимі в неї всередині функції міграції створюється дзеркальна міграція відката.

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

Повторний запуск дата-міграцій

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

Основи Python для школярів від Hillel IT School.
Відкрийте для вашої дитини захопливий світ програмування з нашим онлайн-курсом "Програмування Python для школярів". Ми вивчимо основи програмування на прикладі мови Python, надаючи зрозумілі пояснення та цікаві практичні завдання.
Зареєструватися

 

Завдяки вказівці fake можна відкотити дата-міграцію і запустити її знову. Наприклад, ми промігрували до 8-ї міграції, потім відкотились назад до 7-ї за допомогою фейкової міграції:

 

Після відкочування з оператором fake повернулися на 7-му міграцію, зробили певні зміни у 8-й міграції та повторно запустили міграцію — і все спрацює, як потрібно:

Фейк-міграції

Хочу окремо зосередити вашу увагу на понятті фейк-міграції.

У міграції є два стани:

Англійська для IT від Englishdom.
В межах курсу можна освоїти ключові ІТ-теми та почати без проблем говорити з іноземними колегами.
Дійзнайтеся більше
  1. Суттєвий, який можна побачити всередині папки з файлами migrate (це ті самі файли міграції).
  2. Це запис в базі даних, де також є таблиця з усіма міграціями, які застосували до бази даних.

Коли створюється фейкова міграція, вона додає або видаляє запис із цієї таблиці, але сама міграція не застосовується в жоден із боків.

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

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

Для цього використовуються наступні команди:

manage.py migrate --fake-initial
manage.py migrate --fake myapp
manage.py migrate --fake myapp migration_name

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

Наприклад, так ви можете видалити запис міграції про створення якоїсь моделі. Тоді в таблиці не буде даних про виконання міграції зі створенням цієї моделі — хоча в базі даних ця модель створена. Тому при повторній міграції цієї таблиці може виникнути помилка, пов’язана зі спробою створення поля, яке вже існує в базі даних.

Squashing migrations

І наостанок розповім про сквош-міграції. Цей інструмент дозволяє зробити структуру міграцій більш лаконічною. Під час тривалої розробки застосунку може накопичуватися 100, 200, 300 та більше файлів з міграціями. Це не проблема для Django, він вміє працювати з такою структурою. Однак для програміста це все незручно. Якщо у вас, скажімо, 200 міграцій, а потрібно відкотитися до сотої, а потім назад, то важко все відслідкувати. Для поєднання та оптимізації міграцій роблять сквош-міграцію.

При використанні сквош-міграції можна в автоматичному режимі оптимізувати міграції всього застосунку або вказати, до якої міграції зробити сквошинг.

У такому випадку Django спочатку проаналізує всі міграції, виконані після вказаної міграції (в моєму прикладі — четвертої). Потім програма зробить їх оптимізацію, подивиться, які дії виконувались і чи суперечать вони одна одній, скомпонує все в один файл — і створить сквош-міграцію:

 

Після цього необхідно виконати фіксацію сквош-міграції. Тут маємо кілька кроків:

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

Замість висновку

Як бачите, нічого занадто складного у роботі з такими міграціями, як і з будь-якими іншими, в Django немає. Проте не все так легко, як могло здатися спочатку. І про це важливо пам’ятати кожному Python-розробнику.

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Онлайн-курс Frontend-разробник від Powercode academy.
Курс на якому ти напишеш свій чистий код на JavaScript, попрацюєш із різними видами верстки, а також адаптаціями проектів під будь-які екрани. .
Зарееструватися

Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.

Топ-5 найпопулярніших блогерів вересня

Всего просмотровВсього переглядів
2956
#1
Всего просмотровВсього переглядів
2956
Recruiter| Talent Acquisition Specialist
Всего просмотровВсього переглядів
305
#2
Всего просмотровВсього переглядів
305
Career Consultant в GoIT
Всего просмотровВсього переглядів
108
#3
Всего просмотровВсього переглядів
108
Software Developer у FullCity Consulting
Всего просмотровВсього переглядів
69
#4
Всего просмотровВсього переглядів
69
Всего просмотровВсього переглядів
55
#5
Всего просмотровВсього переглядів
55
Рейтинг блогерів

Найбільш обговорювані статті

Топ текстів

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: