فرض کنید در پنل ادمین جنگو میخواهید نام نویسندهای قدیمی را که دیگر با مجموعه همکاری ندارد حذف کنید؛ اما درست بعد از کلیک روی دکمه حذف، متوجه میشوید صدها مقالهای که او طی سالها نوشته، به همراه تمام نظرات و تگهای مرتبط، ناگهان از بین رفتهاند!
شناخت دقیق آرگومان on_delete در فیلدهای ForeignKey در مدل های Django صرفاً یک جزئیات فنی نیست. این گزینه نقش مهمی در حفظ امنیت و یکپارچگی دادههای پروژه دارد. در ادامه بررسی میکنیم که چطور با انتخاب صحیح این متد، میتوان از بروز چنین اتفاقاتی جلوگیری کرد و دادههای پروژه را ایمن نگه داشت.
پارامتر on_delete در مدل های Django که توی تعریف یک رابطه (relationship) در مدل ها استفاده میشه، نحوه رفتار سیستم در مواقع حذف یک شی مرتبط با شی دیگه رو مشخص میکنه.
متد CASCADE در on_delete در مدل های جنگو
متد CASCADE یکی از رایجترین و در عین حال حساسترین گزینهها در جنگو است. عملکرد آن دقیقاً شبیه به یک زنجیره دومینو است؛ به این معنا که با حذف رکورد والد، تمام رکوردهای فرزندی که به آن وابسته هستند نیز بهصورت خودکار حذف میشوند.
این قابلیت از یک طرف برای پاکسازی خودکار دیتابیس و جلوگیری از باقی ماندن «دادههای یتیم» بسیار کاربردی است، اما از طرف دیگر میتواند حجم زیادی از اطلاعات مهم را از بین ببرد. بنابراین، اگر هدف شما این است که با حذف یک دستهبندی، تمام دادهها و زیرمجموعههای وابسته به آن نیز حذف شوند، CASCADE انتخاب مناسبی خواهد بود.
به بیان ساده: CASCADE یعنی وقتی یک رکورد حذف میشود، تمام رکوردهایی که در مدلهای دیگر به آن وابسته هستند نیز به دنبال آن حذف شوند.
متدهای SET_NULL و SET_DEFAULT: حفظ داده بدون وابستگی
در برخی پروژهها، حذف یک رکورد والد نباید باعث حذف اطلاعات مربوط به فرزند شود. برای مثال، اگر یک نویسنده از سیستم حذف شود، مقالاتی که قبلاً منتشر کرده همچنان برای سایت ارزشمند هستند و نباید همراه با او حذف شوند. در چنین شرایطی، هدف اصلی حفظ دادههای فرزند است.
-
متد SET_NULL: این متد پس از حذف رکورد والد، مقدار فیلد مرتبط در رکوردهای فرزند را به
NULLتغییر میدهد. استفاده از آن برای سیستمهای گزارشدهی و لاگها مناسب است؛ مخصوصاً زمانی که ثبت خودِ «واقعه» اهمیت بیشتری از نگهداری اطلاعات «فاعل» آن دارد. نکته مهم این است که برای استفاده ازSET_NULLباید هنگام تعریف فیلد مدل،null=Trueرا نیز مشخص کنید؛ در غیر این صورت، جنگو نمیتواند مقدارNULLرا در فیلد ذخیره کند. -
متد SET_DEFAULT: اگر ترجیح میدهید پس از حذف والد، فیلد مرتبط خالی نشود و به جای آن یک مقدار مشخص قرار بگیرد، میتوانید از این متد استفاده کنید. برای نمونه، میتوان مقالات نویسنده حذفشده را به یک کاربر سیستمی مانند «نویسنده مهمان» یا «ادمین» نسبت داد. به این ترتیب، ارتباط مقالات با یک نویسنده حفظ میشود و ساختار سایت نیز بدون مشکل ادامه پیدا میکند.
متد های RESTRICT و PROTECT: محافظت هوشمندانه از سلسله مراتب
این بخش را میتوان یکی از نکات حرفهای بحث دانست؛ جایی که تفاوت میان RESTRICT و PROTECT در روابط چندلایه و پیچیده خودش را نشان میدهد. برای درک بهتر، سناریوی «خواننده < آلبوم < آهنگ» را در نظر بگیرید:
- یک خواننده (Artist) داریم که میتواند چندین آلبوم (Album) داشته باشد.
- هر آلبوم نیز شامل چندین آهنگ (Song) است.
- رابطه میان آهنگ و آلبوم با گزینه RESTRICT تعریف شده است.
حالا ببینیم هرکدام از این متدها چه رفتاری دارند:
-
عملکرد PROTECT: این متد مانند یک «دیوار سخت» عمل میکند. اگر بخواهید آلبومی را که هنوز آهنگ دارد حذف کنید، جنگو با خطای
ProtectedErrorعملیات حذف را متوقف میکند. حتی اگر قصد حذف «خواننده» را داشته باشید و آلبومها از طریقCASCADEبه آن متصل باشند، وجود PROTECT در رابطه با آهنگها باعث میشود کل فرآیند متوقف شود. -
عملکرد RESTRICT: این متد را میتوان یک «دیوار هوشمند» دانست. اگر مستقیماً بخواهید آلبومی را که هنوز آهنگ دارد حذف کنید، جنگو مانع این کار میشود. اما تفاوت مهم زمانی مشخص میشود که بخواهید «خواننده» را حذف کنید و آلبومها بهصورت زنجیرهای (
CASCADE) حذف شوند. در این حالت، RESTRICT در لایه آهنگها تشخیص میدهد که حذف آلبوم و آهنگها بخشی از همان عملیات زنجیرهای است و اجازه میدهد خواننده، آلبومها و آهنگها در ادامه همان فرآیند حذف شوند.
در نتیجه، RESTRICT تنها زمانی اجازه حذف را میدهد که حذف رکورد فرزند، بخشی از یک عملیات حذف زنجیرهای (CASCADE) باشد که از یک والد مشترک در سطح بالاتر آغاز شده است. این رفتار هوشمندانه، تفاوت اصلی RESTRICT با PROTECT را در روابط چندسطحی مشخص میکند.
متد های SET و DO_NOTHING: منطق سفارشی و ریسکهای آگاهانه
در سناریوهای پیشرفتهتر توسعه، گاهی لازم است کنترل بیشتری روی نحوه مدیریت روابط دیتابیس داشته باشیم. در چنین شرایطی، متدهای SET() و DO_NOTHING میتوانند کاربرد داشته باشند؛ البته هرکدام با ملاحظات خاص خود.
-
متد
SET(): این متد گزینهای قدرتمند است، زیرا به شما امکان میدهد هنگام حذف یک رکورد، یک تابع (Function) را اجرا کنید. برای نمونه، میتوانید تابعی به نامget_sentinel_userتعریف کنید تا هنگام حذف یک کاربر، رکوردهای مرتبط با او را به پروفایلی مانند «کاربر حذفشده» (Ghost User) منتقل کند. به این ترتیب، روابط دیتابیسی همچنان معتبر باقی میمانند و در عین حال نیازی به حذف فیزیکی دادههای مرتبط نخواهد بود. -
متد
DO_NOTHING: همانطور که از نام آن مشخص است، این گزینه هیچ اقدامی هنگام حذف رکورد والد انجام نمیدهد. استفاده از آن معمولاً توصیه نمیشود، مگر در شرایط خاصی که یکپارچگی دادهها را مستقیماً در سطح دیتابیس (SQL) مدیریت کرده باشید. در غیر این صورت، با حذف رکورد والد، فیلد فرزند همچنان به مقداری اشاره میکند که دیگر وجود ندارد. نتیجه میتواند نقض یکپارچگی دادهها، بروز خطایIntegrityErrorو در نهایت کرش کردن اپلیکیشن باشد.
جمعبندی
انتخاب بین CASCADE یا RESTRICT فقط یک تصمیم فنی ساده نیست؛ این تصمیمی است که آینده دادههای یک کسبوکار را تعیین میکند. به عنوان یک توسعهدهنده، همیشه توصیه میکنم پیش از تعریف هر رابطه، بدترین سناریو (حذف ناگهانی رکورد والد) را تصور کنید. استفاده آگاهانه از گزینههایی مثل SET_NULL یا RESTRICT میتواند مرز بین یک سیستم پایدار و حرفهای با یک سیستم شکننده و آسیبپذیر باشد.