Mengelola skema database dengan makemigrations dan migrate, menulis custom migration serta data migration untuk transformasi data, lalu men- seed skema blog dengan fixtures JSON dan loaddata. Termasuk strategi menggabungkan migrasi antar developer tanpa konflik.

Model di episode 6 sudah didesain dengan rapi, tetapi database belum tahu apa-apa. Di episode ini kita menjalankan migrations — mekanisme Django untuk mengubah skema database secara bertahap dan bisa di-replay — lalu mengisi data awal lewat fixtures.
Mengapa episode ini penting? Karena di sinilah model Python dan database benar-benar bertemu. Migrations adalah satu-satunya cara aman mengubah skema di tim dan di produksi: setiap perubahan direkam sebagai file, di-review, dan dijalankan berurutan. Kesalahan migrasi di produksi bisa berarti downtime atau kehilangan data, jadi memahami cara kerjanya adalah keterampilan wajib.
Model di episode 6 belum pernah dijalankan. Langkah pertamanya: lahirkan file migrasi, lalu terapkan:
python manage.py makemigrations
python manage.py migratemakemigrations membaca perubahan model dan menulis file ke blog/migrations/ — file Python yang menggambarkan perubahan skema, bukan mengeksekusi SQL langsung. migrate lalu menerapkan semua migrasi yang belum dijalankan, termasuk migrasi bawaan Django (admin, auth, sessions).
Verifikasi hasilnya:
python manage.py showmigrations
python manage.py sqlmigrate blog 0001showmigrations menampilkan status semua migrasi (tanda [X] = sudah diterapkan). sqlmigrate menampilkan SQL mentah yang akan dijalankan — cara terbaik mempelajari apa yang sebenarnya terjadi di database. Query SQL yang dihasilkan ORM inilah yang menjadi materi pembahasan kita di episode 14.
Tip
Biasakan berpasangan: makemigrations lalu review isi file migrasi sebelum migrate. File migrasi adalah kode yang akan dijalankan di produksi — membaca diff-nya (lewat git) mencegah kejutan seperti kolom yang salah tipe atau constraint yang tidak sesuai harapan.
Di project dengan banyak developer, dua orang bisa mengubah model bersamaan dan menghasilkan file migrasi dengan nomor yang sama (0002). Cara mengatasinya:
python manage.py makemigrations --check --dry-run
python manage.py makemigrations --merge--check --dry-run gagal (exit code non-zero) jika ada model yang belum dibuatkan migrasinya — bagus untuk CI. --merge menggabungkan migrasi yang bercabang. Alur sehat: commit migrasi sebagai commit terpisah yang jelas, dan segera migrasikan begitu ditarik.
File migrasi adalah Python biasa. Contoh isi hasil makemigrations:
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Category",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=100, unique=True)),
],
),
migrations.CreateModel(
name="Post",
fields=[
("title", models.CharField(max_length=200)),
("category", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="blog.category", related_name="posts")),
],
),
]dependencies mencantumkan migrasi yang harus jalan lebih dulu — termasuk AUTH_USER_MODEL yang kita singgung di episode 10. operations adalah daftar langkah: CreateModel, AddField, RemoveField, AlterField, DeleteModel. Setiap operasi adalah unit yang bisa di-rollback.
Ketika perubahan skema butuh logika yang tidak bisa dinyatakan lewat model, tulis custom migration manual:
python manage.py makemigrations blog --empty -n backfill_post_slugsLalu isi operasinya dengan RunPython:
from django.db import migrations
from django.utils.text import slugify
def backfill_slugs(apps, schema_editor):
Post = apps.get_model("blog", "Post")
for post in Post.objects.all():
if not post.slug:
post.slug = slugify(post.title)
post.save(update_fields=["slug"])
def noop(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [("blog", "0001_initial")]
operations = [
migrations.RunPython(backfill_slugs, noop),
]Warning
Di dalam data migration, selalu pakai apps.get_model("blog", "Post") — bukan import langsung from blog.models import Post. apps.get_model memberi "historical model" sesuai kondisi migrasi saat itu, sehingga migrasi tetap berjalan benar walau model berubah di versi berikutnya. Meng-import langsung adalah salah satu penyebab migrasi rusak yang paling umum.
Perbedaan custom migration di atas dan data migration: keduanya sama-sama RunPython, tetapi data migration fokus pada transformasi/backfill data, sering dipakai saat menambahkan kolom NOT NULL ke tabel yang sudah berisi data. Alur aman yang dipakai di produksi:
AddField dengan null=True (migration 1).RunPython untuk mengisi nilai (migration 2).AlterField ke null=False (migration 3).Tiga langkah ini mencegah kegagalan NOT NULL saat kolom baru ditambahkan ke baris yang sudah ada. Pola ini begitu umum sehingga layak dihafal.
Untuk environment development, kalian butuh data contoh. Fixtures adalah file data (biasanya JSON) yang di-export/import dengan perintah bawaan:
python manage.py dumpdata blog --indent 2 > blog/fixtures/blog.json
python manage.py loaddata blogdumpdata blog mengekspor semua tabel app blog ke JSON. loaddata blog membaca blog/fixtures/blog.json (Django mencari di folder fixtures/ tiap app) dan memasukkannya ke database. Fixtures juga bisa dipakai di manage.py test sebagai data awal — cara cepat menyiapkan data untuk testing di episode 15.
python manage.py migrate
python manage.py loaddata blog
python manage.py createsuperuserUrutan ini menjadi rutinitas "provision database baru": migrasi skema, seed data, lalu buat user admin untuk masuk ke Django Admin episode 8.
Note
Fixtures cocok untuk data referensi (categories, tags) dan data development. Untuk data besar atau migrasi antar sistem yang kompleks, pertimbangkan tool seperti django-seed atau script management command — tetapi untuk lab devblog, fixtures JSON sudah lebih dari cukup.
Sampai sini SQLite melayani lab dengan baik. Saat siap produksi, ganti engine ke PostgreSQL dan install driver:
pip install "psycopg[binary]"DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "devblog",
"USER": "devblog",
"PASSWORD": "rahasia",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}Lalu python manage.py migrate di database PostgreSQL yang baru. Kredensial di atas baru boleh mengarah ke environment variable di episode 16 — jangan pernah hardcode di kode yang ter-commit. Perbedaan perilaku SQLite vs PostgreSQL (misalnya case-sensitivity) akan terasa di episode 14.
Inti yang harus dibawa pulang:
makemigrations menulis file migrasi; migrate menerapkannya; sqlmigrate menunjukkan SQL-nya.--check untuk CI, --merge untuk konflik cabang.RunPython memakai apps.get_model — jangan import model langsung.NOT NULL baru: AddField null → backfill → AlterField.dumpdata/loaddata) untuk seed data development dan referensi.Di episode 8 selanjutnya kita memanfaatkan "batteries-included" yang paling menonjol: Django Admin — mengkustomisasi ModelAdmin dengan list_display, filter, search, inline, dan custom actions untuk membangun admin panel production-grade untuk CMS blog kita.