فصل ۲: مدل‌ها و ORM — طراحی داده‌ی درست

Migrations حرفه‌ای: data migration، squash و حل تداخل

Migration؛ تاریخچه‌ی نسخه‌دار پایگاه داده

هر بار مدل را تغییر می‌دهید، makemigrations یک فایل پایتونی می‌سازد که فقط «تفاوت» را توصیف می‌کند و migrate آن را روی پایگاه داده اجرا می‌کند. این فایل‌ها بخشی از کد پروژه‌اند و باید در git کامیت شوند؛ سرور هرگز نباید خودش makemigrations بزند.

دستورکاربرد
makemigrations ordersساخت migration برای یک اپ
migrateاجرای همه‌ی migrationهای اجرانشده
showmigrations ordersفهرست با علامت [X] برای اجراشده‌ها
sqlmigrate orders 0005SQL دقیقی که اجرا خواهد شد
migrate orders 0004برگشت به یک نقطه‌ی قبلی
migrate --planنمایش ترتیب اجرا بدون اجرا
makemigrations --mergeحل تداخل دو شاخه
squashmigrations orders 0001 0040ادغام چند migration در یکی

Data migration با RunPython

فرض کنید فیلد tracking_code را اضافه کرده‌اید و باید برای هزاران سفارش قدیمی مقدار بسازید. یک migration خالی بسازید و منطق را در آن بنویسید:

python manage.py makemigrations orders --empty --name fill_tracking_codes
from django.db import migrations


def fill_codes(apps, schema_editor):
    Order = apps.get_model("orders", "Order")       # نسخه‌ی تاریخی مدل، نه import مستقیم
    batch = []
    for order in Order.objects.filter(tracking_code="").only("pk").iterator(chunk_size=2000):
        order.tracking_code = f"KSH{order.pk:07d}"
        batch.append(order)
        if len(batch) >= 2000:
            Order.objects.bulk_update(batch, ["tracking_code"])
            batch.clear()
    Order.objects.bulk_update(batch, ["tracking_code"])


class Migration(migrations.Migration):
    dependencies = [("orders", "0006_order_tracking_code")]
    operations = [
        migrations.RunPython(fill_codes, migrations.RunPython.noop),
    ]

آرگومان دوم (noop) اجازه می‌دهد migration برگشت‌پذیر بماند؛ بدون آن migrate orders 0005 با خطای «irreversible» متوقف می‌شود.

تداخل migration در کار تیمی

اگر شما و همکارتان هر دو روی شاخه‌ی خودتان 0007_... ساخته باشید، بعد از merge پیام «Conflicting migrations detected» می‌گیرید. راه‌حل: python manage.py makemigrations --merge که یک migration ادغام با دو وابستگی می‌سازد. اگر هر دو migration یک فیلد را تغییر داده‌اند، قبل از merge یکی را حذف و دوباره بسازید.

نکته‌هایی که کمتر کسی می‌داند

  • در RunPython هرگز مدل را مستقیم import نکنید؛ مدل امروز ممکن است فیلدهایی داشته باشد که در آن نقطه از تاریخ هنوز وجود ندارند. همیشه apps.get_model.
  • متدهای سفارشی مدل و متد save() بازنویسی‌شده در مدل تاریخی وجود ندارند؛ فقط فیلدها و Managerهایی که use_in_migrations = True دارند.
  • makemigrations --check --dry-run در CI اگر مدلی تغییر کرده اما migration ساخته نشده باشد با کد خطا خارج می‌شود؛ جلوی «فراموش کردم migration بسازم» را می‌گیرد.
  • افزودن فیلد NOT NULL به جدول بزرگ را سه‌مرحله‌ای انجام دهید: اول فیلد nullable، بعد data migration، بعد NOT NULL کردن؛ تا جدول مدت طولانی قفل نماند.
  • روی PostgreSQL هر migration در یک تراکنش اجرا می‌شود؛ برای ساختن ایندکس بدون قفل از AddIndexConcurrently با atomic = False استفاده کنید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.