فرض کنید یه فروشگاه دارید و تصمیم میگیرید یه دسته‌بندی قدیمی رو پاک کنید. دکمه حذف رو میزنید، همه‌چیز هم ظاهراً عادیه، ولی چند دقیقه بعد میفهمید یه عالمه محصول وابسته به همون دسته‌بندی هم ناپدید شدن! این دقیقاً از اون باگ‌هاییه که ممکنه فقط با یه تنظیم اشتباه توی مدل‌ها اتفاق بیفته و وقتی متوجهش بشید که دیگه کار از کار گذشته.

وقتی دو مدل در Django به هم مرتبط هستن، باید مشخص کنیم اگر رکورد اصلی حذف شد، رکورد وابسته چه رفتاری داشته باشه. این رفتار با گزینه‌ای به اسم on_delete مشخص میشه.

on_delete دقیقاً چه کاری میکنه؟

فرض کنید مدل Product به مدل Category وصل شده. یعنی هر محصول متعلق به یه دسته‌بندیه.

حالا اگه Category حذف بشه، Django باید تصمیم بگیره با Productهای مربوط به اون چه کاری انجام بده. اینجا چند انتخاب داریم: محصولات رو هم پاک کنه، اجازه حذف Category رو نده یا فقط ارتباط بین اون‌ها رو خالی کنه.

همین تصمیم کوچیک میتونه روی امنیت اطلاعات کل پروژه تأثیر بذاره.

خطرناک‌ترین حالت؛ حذف زنجیره‌ای

یکی از رایج‌ترین گزینه‌ها حالتیه که میگه «اگه رکورد اصلی حذف شد، رکوردهای وابسته رو هم حذف کن». اسم این رفتار حذف زنجیره‌ای یا CASCADE هست.

مثلاً:

class Category(models.Model):
    name = models.CharField(max_length=100)


class Product(models.Model):
    name = models.CharField(max_length=100)

    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT
    )

اینجا به جای حذف زنجیره‌ای، از PROTECT استفاده کردیم؛ یعنی تا وقتی محصولی به اون Category وصل باشه، Django اجازه حذف دسته‌بندی رو نمیده.

این رفتار برای اطلاعات مهم معمولاً خیلی امن‌تره.

CASCADE چه زمانی خوبه؟

حذف زنجیره‌ای همیشه بد نیست. گاهی دقیقاً همون چیزیه که نیاز داریم.

مثلاً یه مدل اصلی داریم به اسم Order و یه مدل وابسته برای جزئیات سفارش. اگه خود Order حذف بشه، منطقیه جزئیات مربوط به همون سفارش هم حذف بشن؛ چون بدون سفارش اصلی دیگه معنی خاصی ندارن.

مشکل زمانی شروع میشه که بدون فکر روی همه رابطه‌ها CASCADE بذاریم.

مثلاً حذف User باعث حذف Orderها بشه، حذف Order باعث حذف Paymentها بشه و حذف Payment هم به رکوردهای دیگه وصل باشه. یه حذف ساده میتونه تبدیل به یه زنجیره بزرگ از حذف اطلاعات بشه.

PROTECT یعنی جلوی حذف رو بگیر

گاهی رکورد وابسته اونقدر مهمه که اصلاً نباید اجازه بدیم رکورد اصلی حذف بشه.

مثلاً یه Category داریم که هنوز ۵۰۰ محصول داخلشه. اگه کاربر بخواد Category رو حذف کنه، منطقی‌تره سیستم بگه:

«اول تکلیف محصولات رو مشخص کن.»

این همون کاریه که PROTECT انجام میده. تا وقتی رکورد وابسته وجود داره، حذف رکورد اصلی متوقف میشه.

برای اطلاعات حساس مثل سفارش، پرداخت، فاکتور و داده‌های مالی، این رفتار خیلی وقت‌ها انتخاب مطمئن‌تریه.

SET_NULL یعنی ارتباط رو قطع کن

یه حالت دیگه اینه که رکورد اصلی حذف بشه ولی رکورد وابسته باقی بمونه.

مثلاً یه مقاله به نویسنده وصل شده، ولی اگه حساب نویسنده حذف شد نمیخوایم مقاله هم پاک بشه. فقط میخوایم بخش نویسنده خالی بشه.

این حالت با SET_NULL انجام میشه.

البته برای استفاده ازش باید فیلد رابطه اجازه مقدار خالی داشته باشه. یعنی از نظر مدل باید تعریف کرده باشید که این ارتباط میتونه بدون مقدار هم باقی بمونه.

RESTRICT چه فرقی داره؟

یه گزینه دیگه هم وجود داره که مفهومش «محدود کردن حذف» هست و با RESTRICT شناخته میشه.

رفتارش در خیلی از سناریوها شبیه PROTECT به نظر میرسه؛ یعنی اجازه نمیده رکوردی حذف بشه که هنوز رکوردهای دیگه بهش وابسته هستن. تفاوت اصلی تو بعضی حذف‌های زنجیره‌ای پیچیده‌تر خودش رو نشون میده.

برای پروژه‌های معمولی، مهم‌ترین مسئله اینه که بدونید حذف رکورد اصلی نباید همیشه خودکار باشه.

اشتباه بزرگ؛ انتخاب CASCADE فقط چون ساده‌تره

یکی از رایج‌ترین اشتباه‌ها اینه که هنگام ساخت مدل میگیم:

«فعلاً CASCADE بذار، بعداً درستش میکنیم.»

و اون «بعداً» معمولاً هیچ‌وقت نمیرسه!

چند ماه بعد پروژه بزرگ شده، هزاران رکورد داخل دیتابیس دارید و یه نفر یه رکورد رو از پنل مدیریت پاک میکنه. تازه اون موقع مشخص میشه رابطه‌هایی که اول پروژه بی‌اهمیت به نظر میرسیدن، الان به اطلاعات مهم وصل شدن.

بهتره همون موقع طراحی مدل از خودتون بپرسید:

اگه این رکورد حذف بشه، واقعاً رکورد وابسته هم باید نابود بشه؟

اگه جواب صددرصد «بله» نیست، CASCADE احتمالاً نیاز به بررسی بیشتری داره.

حذف نرم هم بعضی وقت‌ها انتخاب بهتریه

بعضی اطلاعات اصلاً نباید واقعاً از دیتابیس پاک بشن.

مثلاً سفارش‌های مالی، تراکنش‌ها یا گزارش‌های مهم ممکنه برای حسابداری یا بررسی‌های بعدی لازم باشن. در چنین شرایطی به جای حذف واقعی، میشه یه فیلد مثل is_deleted یا is_active داشت و رکورد رو فقط غیرفعال کرد.

به این روش حذف منطقی بدون پاک کردن واقعی رکورد گفته میشه که معمولاً با اصطلاح Soft Delete شناخته میشه.

این مدل برای داده‌های حساس خیلی امن‌تر از حذف فیزیکی دائمیه.

قبل از حذف اطلاعات مهم، تست کنید

هر رابطه‌ای که روی داده مهم اثر میذاره باید تست بشه. مخصوصاً وقتی چند مدل به شکل زنجیره‌ای به هم وصلن.

بهتره قبل از Deploy بررسی کنید که حذف یه User، Category یا Order دقیقاً چه رکوردهایی رو تحت تأثیر قرار میده. این کار خیلی ساده‌تر از بازیابی یه دیتابیس آسیب‌دیده بعد از یه حذف اشتباهه.

حتی داخل پنل مدیریت Django هم بهتره برای حذف‌های حساس تأییدیه واضح داشته باشید و کاربر بدونه چه اطلاعاتی قراره پاک بشن.

جمع بندی

on_delete شاید فقط یه پارامتر کوچیک داخل ForeignKey به نظر برسه، ولی میتونه مشخص کنه حذف یه رکورد فقط همون یک رکورد رو پاک کنه یا یه زنجیره کامل از اطلاعات رو با خودش ببره.

برای داده‌هایی که واقعاً وابسته و بی‌استفاده میشن، CASCADE انتخاب خوبیه. وقتی نباید رکورد اصلی تا زمان وجود داده وابسته حذف بشه، PROTECT یا RESTRICT منطقی‌تره و وقتی رکورد وابسته باید باقی بمونه، SET_NULL میتونه انتخاب مناسبی باشه.

مهم‌ترین نکته اینه که CASCADE رو فقط چون سریع و راحت کار میکنه انتخاب نکنید. قبلش دقیقاً مشخص کنید بعد از حذف هر رکورد، چه اتفاقی باید برای داده‌های وابسته بیفته.