Cara Membuat Data Migration dengan Django

Bagus Aji Santoso 3 Februari 2018

Cara Membuat Data Migration dengan Django

Diterjemahkan dari How to Create Django Data Migrations karya Vitor Freitas.

Level menengah/intermediate. Pembaca diharapkan sudah memahami cara membuat aplikasi web Django.

Data Migration alias migrasi data merupakan sebuah cara yang sangat nyaman dilakukan untuk mengubah data di database untuk mengikuti perubahan schema. Cara kerjanya sama seperti migrasi schema biasa. Django mencatat tiap dependensi, urutan eksekusi dan memeriksa apakah aplikasi untuk melakukan migrasi data tertentu atau belum.

Kasus paling sering ialah saat kita ingin membuat field baru yang non-nullable. Atau saat kita akan membuat field baru untuk menyimpan angka pencatat (cached count), kita akan membuat field baru tersebut dan memulai angka pencatat pertamanya.

Di artikel ini kita akan mempelajari cara kerja sebuah aplikasi sederhana yang bisa di-extend dan modifikasi sesuai keperluan.

Data Migrations

Anggap kita memiliki sebuah app bernama blog yang sudah dipasang di INSTALLED_APPS.

Aplikasi blog kita memiliki model sebagai berikut:

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

Aplikasi ini sudah memakai model Post di atas; ia sudah ada di production dan sudah ada banyak data tdi dalamnya.

idtitledatecontent
1How to Render Django Form Manually2017-09-26 11:01:20.547000[…]
2How to Use Celery and RabbitMQ with Django2017-09-26 11:01:39.251000[…]
3How to Setup Amazon S3 in a Django Project2017-09-26 11:01:49.669000[…]
4How to Configure Mailgun To Send Emails in a Django Project2017-09-26 11:02:00.131000[…]

Sekarang, kita anggap kita akan membuat field baru bernama slug yang akan dipakai untuk membuat URL blog tersebut. Field slug ini harus unique dan not null.

Secara umum, selalu buat field baru dengan properti null=True atau dengan sebuah nilai default. Jika tidak bisa menyelesaikannya dengan parameter default, pertama buat field dengan properti null=True dan buat migrasi datanya. Setelah itu kita bisa membuat migrasi baru dan mengubah properti field-nya dengan null=False.

Begini caranya:

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

Buat migrasi:

python manage.py makemigrations blog

Migrations for 'blog':
  blog/migrations/0002_post_slug.py
    - Add field slug to post

Lalu aplikasikan:

python manage.py migrate blog

Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0002_post_slug... OK

Sampai di sini, sistem database kita sudah memiliki kolom slug.

idtitledatecontentslug
1How to Render Django Form Manually2017-09-26 11:01:20.547000[…](null)
2How to Use Celery and RabbitMQ with Django2017-09-26 11:01:39.251000[…](null)
3How to Setup Amazon S3 in a Django Project2017-09-26 11:01:49.669000[…](null)
4How to Configure Mailgun To Send Emails in a Django Project2017-09-26 11:02:00.131000[…](null)

Buat sebuah empty migration dengan perintah berikut:

python manage.py makemigrations blog --empty

Migrations for 'blog':
  blog/migrations/0003_auto_20170926_1105.py

Lalu buka file 0003_auto_20170926_1105.py, dan tambahkan konten berikut:

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 = [
    ]

Lalu di dalam file ini, kita bisa membuat sebuah fungsi yang akan diekseskusi oleh perintah 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),
    ]

Pada contoh di atas kita menggunakna fungsi slugify. Ia meminta sebuah string sebagai parameter dan mengubahnya menjadi sebuah slug. Berikut ini contoh penggunaannya:

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'

Fungsi yang dipanggil RunPython untuk membuat sebuah migrasi data meminta dua parameter: apps dan schema_editor. Parameter RunPython mengisikan dua parameter tersebut. Ingat juga bahwa kita perlu mengimpor model dengan method apps.get_model('app_name', 'model_name').

Simpan file ini dan eksekusi migrasinya seperti melakukan migrasi model:

python manage.py migrate blog
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0003_auto_20170926_1105... OK

Sekarang jika kita periksa isi databasenya:

idtitledatecontentslug
1How to Render Django Form Manually2017-09-26 11:01:20.547000[…]how-to-render-django-form-manually
2How to Use Celery and RabbitMQ with Django2017-09-26 11:01:39.251000[…]how-to-use-celery-and-rabbitmq-with-django
3How to Setup Amazon S3 in a Django Project2017-09-26 11:01:49.669000[…]how-to-setup-amazon-s3-in-a-django-project
4How to Configure Mailgun To Send Emails in a Django Project2017-09-26 11:02:00.131000[…]how-to-configure-mailgun-to-send-emails-in-a-django-project

Setiap Post memiliki sebuah value, jadi kita bisa mengubahnya dari null=True menjadi null=False. Dan karena semua value unique, kita juga bisa menambah tag unique=True.

Ubah model menjadi:

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

Buat migrasi baru:

python manage.py makemigrations blog

Sekarang akan muncul prompt berikut:

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:

Pilih opsi 2 dengan menulis angka “2” di terminal.

Migrations for 'blog':
  blog/migrations/0004_auto_20170926_1422.py
    - Alter field slug on post

Sekarang kita bisa mengaplikasikan migrasinya:

python manage.py migrate blog
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0004_auto_20170926_1422... OK

Kesimpulan

Migrasi data terkadang sulit dilakukan. Saat membuat mgirasi data untuk project kita, selalu periksa data production dengan teliti. Impelemntasi slugify_title yang penulis pakai di contoh ini terlalu sederhana karena ia bisa menulis title ganda di data yang besar. Selalu lakukan test untuk migrasi data di staging environment, untuk menghindari kerusakan data di production.

Penting untuk melakukannya step-by-step, sehingga kita memiliki kontrol setiap perubahan yang ingin dilakukan. Catat bahwa di sini penulis membuat tiga file migrasi untuk sebuah migrasi data sederhana.

Seperti yang dapat pembaca lihat, melakukan migrasi data seperti tidak sulit dilakukan. Ia juga sangat fleksibel. Kita bisa saja memuat sebuah file teks untuk mengisikan datanya ke sebuah kolom baru.

Kode sumber yang dipakai di artikel ini tersedia di GitHub: https://github.com/sibtc/data-migrations-example