Идемпотентные дата-миграции
Дата-миграции часто используются для добавления новых записей. Если делать это с помощью метода create
, то запустить её повторно будет весьма проблематично. Каждый новый запуск создает в таблице всё больше дублей. Если миграция не добавляет записи, а удаляет их, то проблемой станут вызовы get
— при повторном запуске они ничего не найдут и сломают код.
Идемпотентными называются такие программы, запуск которых всегда приводит к одному и тому же результату вне зависимости от того первый это запуск или уже десятый. Дата-миграцию можно переписать так, чтобы она стала идемпотентной. Это очень удобно в отладке. К тому же идемпотентные миграции намного устойчивее и безопаснее для запуска на “боевом” сервере.
Как делать не надо
У вас есть сайт школьной библиотеки и вы хотите пополнить базу новой коллекцией книг. От администратора приходит текстовый файл с новыми книгами, вы садитесь писать дата-миграцию:
from django.db import migrations
def load_books(apps, schema_editor):
Book = apps.get_model('books', 'Book')
with open('books.txt', 'r') as file:
new_books = file.readlines()
for title in new_books:
Book.objects.create(title=title)
class Migration(migrations.Migration):
dependencies = [
('appname', '0009_auto'),
]
operations = [
migrations.RunPython(load_books),
]
Код запустился, книги добавлены в тестовую БД, всё готово. Вы обновляете код на сервере, запускаете migrate
, и, на всякий случай, заглядываете в админку проверить что всё в порядке. Как оказалось, заглянули вы не зря — администратор библиотеки не стал вас ждать и пока вы писали миграцию сам загрузил в базу пару десятков книг из файла books.txt
. Не все книги в books.txt
оказались новыми, в базе появились десятки дублей, все их придется удалять вручную. Ох уж этот администратор, чёрт его дернул лезть в базу без спроса …
Но корень проблемы не в администраторе. Между моментом написания дата-миграции и её запуском всегда проходит время, иногда довольно много. В коде миграции вы не можете рассчитывать на наличие или обязательное отсутствие нужных вам записей. С базой данных работает много людей и все их действия вы не проконтролируете.
Новый вариант
Чтобы избежать дублирования записей вы можете сналача проверить их наличие в БД, прежде чем вызывать create
:
def load_books(apps, schema_editor):
Book = apps.get_model('books', 'Book')
with open('books.txt', 'r') as file:
new_books = file.readlines()
for title in new_books:
if not Book.objects.filter(title=title).exists():
Book.objects.create(title=title)
Этот подход мешает плодиться дублям и надежно решает проблему. А чтобы не писать каждый раз одни и те же проверки воспользуйтесь специальным методом get_or_create
, он всё сделает за вас:
def load_books(apps, schema_editor):
...
for title in new_books:
Book.objects.get_or_create(title=title)
У этого подхода есть еще одно преимущество — он позволяет запускать дата-миграции многократно без предварительной очистки БД от старых записей. Это очень удобно при отладке в сочетании с migrate --fake
.
Сложный случай
Модель данных редко хранит одно единственное поле title
, обычно там еще много чего есть: автор книги, издательство, обложка, год издательства и не только. Рассмотренный пример кода с новыми книгами слишком упрощен, но метод get_or_create
может справиться и со сложными случаями. Если уникальными полями будет сочетание “название — год”, то код будет выглядеть так:
def load_books(apps, schema_editor):
...
for title, year, pages_number in new_books:
Book.objects.get_or_create(title=title, year=year, defaults={
'pages_number': pages_number,
'present': True,
})
Дата-миграция сначала будет искать в базе данных книгу с указанным title
и year
. Если найдет, то на этом всё и закончится. Если нет — создаст новую книгу с указанными title
, year
и со всеми полями из словаря defaults
.
Помимо get_or_create
Django предлагает похожий метод update_or_create
. Работает он ровно так, как обещает его название. И это прекрасно.
Что почитать
- Документация к get_or_create
- Документация к update_or_create
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.