Як робити міграції даних в Django

8 хв. читання

Міграція – зручний спосіб змінювати дані в БД під час структурних змін. Ось як працює звичайна структурна міграція. 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

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 869
Приєднався: 1 рік тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід