تصور کن مدیر یک هتل ۵ ستاره فوق ‌لوکس هستی (همان Views یا منطق اصلی برنامه). هر بار که یک «مهمان ویژه» (User) وارد هتل میشه و اتاق میگیره، کلی کار باید انجام بشه: ۱. دربان باید چمدان ‌ها رو ببره. ۲. آشپزخانه باید سبد میوه خوش‌آمدگویی آماده کنه. ۳. بخش حسابداری باید پرونده مالی باز کنه. ۴. بخش ایمیل باید پیام "به هتل ما خوش آمدید" بفرسته.

در حالت عادی (بدون سیگنال)، مدیر هتل (شما) مجبورید بعد از ورود مسافر، شخصاً بدوید توی آشپزخانه داد بزنید "میوه بیارید!"، بعد بدوید حسابداری بگید "پرونده بسازید!" و... . این یعنی یک مدیر خسته، کدی شلوغ و درهم ‌تنیده که اگر فردا بخواهید بخش "ماساژ" را هم اضافه کنید، باید دوباره کد های اصلی مدیریت را دستکاری کنید.

اینجاست که Signals (سیگنال‌ها) مثل یک سیستم پیجینگ یا بی ‌سیم پیشرفته وارد صحنه میشه. مدیر هتل فقط کلید اتاق رو تحویل میده و یک دکمه زنگ را فشار میده (میگه: مهمان آمد!). هر بخش (دربان، آشپز، حسابدار) که صدای زنگ رو بشنوه، کار خودش رو انجام میده، بدون اینکه مدیر درگیر جزئیات بشه.

سیگنال (Signal) چیست؟ 

سیگنال‌ها در جنگو پیاده ‌سازی الگوی طراحی «ناظر» (Observer Pattern) هستند. هدف اصلی آن‌ ها Decoupling یا همان جدا کردن اجزای سیستم از همدیگر است. جنگو به ما میگه: "وقتی اتفاق X افتاد، خبر بده تا هر کسی که گوشش به زنگه، کار Y رو انجام بده."

تفاوت اصلی کجاست؟

  • روش سنتی (Hard-coded): توابع مستقیماً همدیگر را صدا میزنند. (کدها به هم چسبیده‌اند، تغییر سخت است).

  • روش سیگنالی (Loosely Coupled): فرستنده (Sender) اصلاً نمی‌داند گیرنده (Receiver) کیست یا کجاست. فقط فریاد می‌زند "این اتفاق افتاد!" و گیرنده‌ها خودکار وارد عمل می‌شوند.

تفاوت: روش کلاف سردرگم vs روش هوشمند

بیایید ببینیم وقتی می‌خواهیم بعد از ثبت‌ نام کاربر، برایش یک "پروفایل" بسازیم چه اتفاقی می‌افتد:

بدون سیگنال (روش سنتی): کاربر فرم ثبت‌ نام را پر می‌کند -> جنگو کاربر را ذخیره می‌کند (User.save) -> بلافاصله در همان خط بعدی، تابع ساخت پروفایل صدا زده می‌شود -> تابع ارسال ایمیل صدا زده می‌شود. نتیجه: فایل views.py شما پر از کار های متفرقه می‌شود که ربطی به هم ندارند. اگر سیستم پروفایل خراب شود، ثبت‌نام کاربر هم با ارور مواجه می‌شود.

با سیگنال (The Django Way): کاربر فرم را پر می‌کند -> جنگو کاربر را ذخیره می‌کند و تمام! (ویو کارش تمام شد). در پشت صحنه:لحظه‌ای که User ذخیره شد، جنگو یک سیگنال به نام post_save شلیک می‌کند. یک تکه کد جداگانه که گوش‌به‌زنگ نشسته، این شلیک را می‌بیند و می‌گوید: "آهای! یوزر جدید اومد، من برم پروفایلش رو بسازم." نتیجه: کدهای شما تمیز، جدا از هم و قابل مدیریت هستند.

پیاده‌سازی سیگنال در جنگو

بیایید دست به کد بشیم. فرض کنید می‌خواهیم به محض اینکه یک کاربر جدید (User) در سایت ثبت‌نام کرد، خودکار برایش یک مدل Profileهم ساخته شود.

۱. ایمپورت‌های ضروری

معمولاً این کدها را در فایلی به نام signals.py یا در انتهای models.py می‌نویسیم.

from django.db.models.signals import post_save # نوع سیگنال: بعد از ذخیره شدن
from django.contrib.auth.models import User    # فرستنده: مدل یوزر
from django.dispatch import receiver           # گیرنده: دکوریتور دریافت‌کننده
from .models import Profile                    # مدلی که باید ساخته شود

۲. نوشتن گیرنده (Receiver)

این همان کارمند وظیفه‌شناسی است که گوش‌به‌زنگ ایستاده:

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    """
    sender: مدلی که سیگنال فرستاده (User)
    instance: نمونه‌ یوزر خاصی که الان ذخیره شده
    created: یک بولین (True/False). اگر True باشد یعنی یوزر جدید است (Update نیست)
    """
    if created:
        Profile.objects.create(user=instance)
        print(f"پروفایل برای کاربر {instance.username} با موفقیت ساخته شد!")

۳. تحلیل فنی: چه اتفاقی افتاد؟

  • @receiver(post_save, sender=User): این خط میگوید: "ای تابع پایین ‌دستی! هر وقت مدل User متد save() اش با موفقیت تمام شد (post_save)، تو اجرا شو."

  • if created: این خیلی مهم است. متد save() هم موقع ساختن کاربر جدید اجرا می‌شود و هم موقع ویرایش اطلاعات کاربر قدیمی. ما فقط می‌خواهیم وقتی کاربر جدید است (created=True) پروفایل بسازیم، نه هر بار که پسوردش را عوض کرد.

۴. نکته حیاتی: اتصال سیم‌ها (App Config)

اگر کد بالا را در فایلی جداگانه مثل signals.py نوشتید، جنگو به صورت پیش‌فرض روحش هم از وجود آن خبر ندارد! باید در فایل apps.pyاپلیکیشن خود، آن را معرفی کنید:

# users/apps.py
from django.apps import AppConfig

class UsersConfig(AppConfig):
    name = 'users'

    def ready(self):
        import users.signals  # اینجا سیگنال‌ها را بیدار می‌کنیم

حالا وقتی سرور ران شود، سیستم شنود سیگنال ‌ها فعال می‌شود.

انواع سیگنال‌های پرکاربرد در جنگو

انواع سیگنال‌های پرکاربرد

جنگو فقط post_save ندارد. جعبه ابزار شما پر از سنسور های مختلف است:

  • pre_save: قبل از اینکه داده در دیتابیس ذخیره شود (مثلاً برای پر کردن خودکار یک فیلد خالی یا چک کردن قوانین خاص).

  • post_delete: بعد از اینکه یک رکورد پاک شد (مثلاً: کاربر پاک شد، عکس پروفایلش را هم از روی هارد پاک کن).

  • m2m_changed: وقتی رابطه Many-to-Many تغییر کرد (مثلاً تگ‌های یک مقاله عوض شد).

  • جمع‌بندی

    استفاده از سیگنال‌ها در جنگو مثل داشتن یک خانه هوشمند است که با دست زدن چراغ‌هایش روشن می‌شود. خیلی جذاب و تمیز است، اما زیاده‌روی نکنید!

    چرا؟ چون سیگنال‌ ها "نامرئی" هستند. اگر فردا یک برنامه ‌نویس جدید بیاید و ببیند با ساخت یوزر، هزار تا اتفاق عجیب (ایمیل، اس‌ام‌اس، کسر موجودی و...) رخ میدهد ولی در کد View هیچ خبری از این‌ها نیست، گیج میشود (به این می‌گویند Spooky action at a distance).

    قانون طلایی:

    • اگر منطق کاری مستقیماً مربوط به همان اکشن است (مثل پر کردن فیلد updated_at) -> متد save() مدل را Override کنید.

    • اگر منطق کاری مربوط به مدل‌های دیگر است و می‌خواهید وابستگی (Coupling) ایجاد نشود -> از Signal استفاده کنید.

    پس اگر جنگو کار میکنید و میخواهید کد هایتان مثل یک ساعت دقیق و منظم کار کنند، بدون اینکه اجزای مختلف توی دست‌ و پای هم بپیچند، Django Signals همان دستیار مخفی و کار بلد شماست.