Міграція – зручний спосіб змінювати дані в БД під час структурних змін. Ось як працює звичайна структурна міграція. Django відслідковує залежності, порядок виконання і, якщо програма вже застосовувалася, визначає чи була міграція потрібних даних.
Звичайним прикладом міграції даних є ситуація, коли нам потрібно ввести нові поля, що не є нулевими. Чи коли ми створюємо нове поле для зберігання кешованого підрахунку чого-небудь, тобто ми можемо створити нове поле і додати початкове значення підрахунку. В цій статті ми будемо досліджувати простий приклад, який ви зможете змінити та розширити для ваших потреб.
Міграції даних
Припустимо, що в нас є застосунок з назвою blog, який інстальовано в INSTALLED_APPS
нашого проекту.
Наш blog має наступне визначення моделі:
blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
def __str__(self):
return self.title
Застосунок вже використовує Post модель; вона в роботі і ми маємо багато збереженої інформації в БД.
id | title | date | content |
---|---|---|---|
1 | How to Render Django Form Manually | 2017-09-26 11:01:20.547000 | […] |
2 | How to Use Celery and RabbitMQ with Django | 2017-09-26 11:01:39.251000 | […] |
3 | How to Setup Amazon S3 in a Django Project | 2017-09-26 11:01:49.669000 | […] |
4 | How to Configure Mailgun To Send Emails in a Django Project | 2017-09-26 11:02:00.131000 | […] |
Тепер додамо нове поле з назвою slug , яке буде використовуватися для запису нових URL нашого блога. Поле slug має бути унікальним та не бути нулевим.
Крім того, додамо нові поля типу null=True
чи зі значенням default
. Якщо ми не можемо вирішити проблему з параметром default
, то нам необхідно спершу створити поле null=True
, а потім проводити міграцію даних. Після створення нової міграції змінюємо значення поля null=False
.
Ось як ми це можемо зробити:
blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
slug = models.SlugField(null=True)
def __str__(self):
return self.title
Створюємо міграцію:
python manage.py makemigrations blog
Migrations for 'blog':
blog/migrations/0002_post_slug.py
- Add field slug to post
Застосовуємо її:
python manage.py migrate blog
Operations to perform:
Apply all migrations: blog
Running migrations:
Applying blog.0002_post_slug... OK
І після цього база даних вже має стовпчик slug.
id | title | date | content | slug |
---|---|---|---|---|
1 | How to Render Django Form Manually | 2017-09-26 11:01:20.547000 | […] | (null) |
2 | How to Use Celery and RabbitMQ with Django | 2017-09-26 11:01:39.251000 | […] | (null) |
3 | How to Setup Amazon S3 in a Django Project | 2017-09-26 11:01:49.669000 | […] | (null) |
4 | How to Configure Mailgun To Send Emails in a Django Project | 2017-09-26 11:02:00.131000 | […] | (null) |
Створимо пусту міграцію за допомогою наступної команди:
python manage.py makemigrations blog --empty
Migrations for 'blog':
blog/migrations/0003_auto_20170926_1105.py
Тепер відкриємо файл 0003_auto_20170926_1105.py
, який має наступний вигляд:
blog/migrations/0003_auto_20170926_1105.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-26 11:05
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_post_slug'),
]
operations = [
]
Далі в цьому ж файлі ми можемо створити функцію, яка буде запускатися командою RunPython
:
blog/migrations/0003_auto_20170926_1105.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-26 11:05
from __future__ import unicode_literals
from django.db import migrations
from django.utils.text import slugify
def slugify_title(apps, schema_editor):
'''
We can't import the Post model directly as it may be a newer
version than this migration expects. We use the historical version.
'''
Post = apps.get_model('blog', 'Post')
for post in Post.objects.all():
post.slug = slugify(post.title)
post.save()
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_post_slug'),
]
operations = [
migrations.RunPython(slugify_title),
]
У вищенаведеному прикладі ми використовуємо функцію-утиліту slugify
. Вона використовує рядок як параметр і перетворює його.
Подивімось наступний приклад:
from django.utils.text import slugify
slugify('Hello, World!')
'hello-world'
slugify('How to Extend the Django User Model')
'how-to-extend-the-django-user-model'
В будь-якому випадку, функція, що використовується з методом RunPython
для створення міграції даних, очікує два параметри: застосунок та редактор схеми. RunPython
буде опрацьовувати ці параметри. Також необхідно імпортувати моделі використовуючи метод apps.get_model('app_name', 'model_name')
.
Збережіть та проведіть міграцію так, як би ви це робили при проведенні міграції для звичайної моделі:
python manage.py migrate blog
Operations to perform:
Apply all migrations: blog
Running migrations:
Applying blog.0003_auto_20170926_1105... OK
Тепер перевіримо БД:
id | title | date | content | slug |
---|---|---|---|---|
1 | How to Render Django Form Manually | 2017-09-26 11:01:20.547000 | […] | how-to-render-django-form-manually |
2 | How to Use Celery and RabbitMQ with Django | 2017-09-26 11:01:39.251000 | […] | how-to-use-celery-and-rabbitmq-with-django |
3 | How to Setup Amazon S3 in a Django Project | 2017-09-26 11:01:49.669000 | […] | how-to-setup-amazon-s3-in-a-django-project |
4 | How to Configure Mailgun To Send Emails in a Django Project | 2017-09-26 11:02:00.131000 | […] | how-to-configure-mailgun-to-send-emails-in-a-django-project |
Кожен Post (запис) тепер має значення, а ми можемо безпечно змінити значення null=True
на null=False
. І, оскільки всі значення тепер унікальні, ми можемо також додати флаг unique=True
.
Змінимо модель:
blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
slug = models.SlugField(null=False, unique=True)
def __str__(self):
return self.title
Створимо нову міграцію:
python manage.py makemigrations blog
Цього разу ми побачимо наступне:
You are trying to change the nullable field 'slug' on post to non-nullable without a default; we can't do that
(the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL
operation to handle NULL values in a previous data migration)
3) Quit, and let me add a default in models.py
Select an option:
Оберемо опцію 2 набравши «2» в терміналі.
Migrations for 'blog':
blog/migrations/0004_auto_20170926_1422.py
- Alter field slug on post
Тепер ми можемо безпечно застосувати міграцію.
python manage.py migrate blog
Operations to perform:
Apply all migrations: blog
Running migrations:
Applying blog.0004_auto_20170926_1422... OK
Висновок
Міграції даних часом можуть виглядати складними. Коли ви створюєте міграції даних для ваших проектів, завжди спершу перевіряйте вивід програми. Імплементація slugify_title
, яку я використовував в прикладі є трохи спрощеною, тому що вона може генерувати дублікати для великої вибірки. Завжди перевіряйте міграції даних в тестовому середовищі, для того, щоб не зламати щось у продакшні.
Також важливо проводити міграції крок за кроком так, щоб ви могли контролювати зміни, які ви вносите. Зауважте, що тут я створив три файли міграції для простої міграції даних.
Як можна побачити, даний тип міграції досить просто проводити. Цей метод дуже гнучкий. Ви можете для прикладу завантажити будь-який текстовий файл та вставити його зміст в нову колонку.
Вихідний код з цієї статті доступний на GitHub https://github.com/sibtc/data-migrations-example
Ще немає коментарів