Apa itu Django Signals?
Django Signals adalah sistem notifikasi yang memungkinkan aplikasi untuk berkomunikasi secara loosely coupled. Signals memungkinkan kode tertentu dijalankan ketika tindakan tertentu terjadi di tempat lain dalam aplikasi.
Jenis-jenis Signals
Model Signals
1. pre_save
Dikirim sebelum model disimpan ke database.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save, sender=MyModel)
def my_handler(sender, instance, **kwargs):
# Logika sebelum save
instance.slug = instance.title.lower().replace(' ', '-')
2. post_save
Dikirim setelah model disimpan ke database.
from django.db.models.signals import post_save
@receiver(post_save, sender=MyModel)
def create_profile(sender, instance, created, **kwargs):
if created:
# Logika setelah objek baru dibuat
UserProfile.objects.create(user=instance)
3. pre_delete
Dikirim sebelum model dihapus.
from django.db.models.signals import pre_delete
@receiver(pre_delete, sender=MyModel)
def backup_before_delete(sender, instance, **kwargs):
# Backup data sebelum dihapus
BackupModel.objects.create(original_data=instance.data)
4. post_delete
Dikirim setelah model dihapus.
from django.db.models.signals import post_delete
@receiver(post_delete, sender=MyModel)
def cleanup_files(sender, instance, **kwargs):
# Hapus file terkait
if instance.image:
instance.image.delete(save=False)
5. m2m_changed
Dikirim ketika ManyToManyField berubah.
from django.db.models.signals import m2m_changed
@receiver(m2m_changed, sender=MyModel.tags.through)
def tags_changed(sender, instance, action, pk_set, **kwargs):
if action == "post_add":
# Logika ketika tag ditambahkan
pass
Cara Implementasi
1. Menggunakan Decorator @receiver
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def user_saved(sender, instance, created, **kwargs):
if created:
print(f"User baru: {instance.username}")
2. Menggunakan connect()
from django.db.models.signals import post_save
def user_saved(sender, instance, created, **kwargs):
if created:
print(f"User baru: {instance.username}")
post_save.connect(user_saved, sender=User)
3. Registrasi di apps.py
# apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
import myapp.signals # Import file signals
Best Practices
1. Pisahkan File Signals
Buat file signals.py
terpisah untuk menjaga kode tetap terorganisir.
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import MyModel
@receiver(post_save, sender=MyModel)
def handle_save(sender, instance, created, **kwargs):
# Handler logic
pass
2. Hindari Infinite Loops
@receiver(post_save, sender=MyModel)
def avoid_loop(sender, instance, created, **kwargs):
if not hasattr(instance, '_signal_processed'):
instance._signal_processed = True
# Proses yang aman
instance.save() # Ini bisa menyebabkan loop
3. Gunakan weak=False untuk Persistent Connections
post_save.connect(my_handler, sender=MyModel, weak=False)
4. Handle Exceptions
@receiver(post_save, sender=MyModel)
def safe_handler(sender, instance, created, **kwargs):
try:
# Operasi yang mungkin gagal
external_api_call(instance.data)
except Exception as e:
logger.error(f"Signal handler error: {e}")
Custom Signals
Membuat Signal Custom
# signals.py
import django.dispatch
# Definisi signal custom
user_logged_in = django.dispatch.Signal()
# Mengirim signal
user_logged_in.send(sender=self.__class__, user=user, request=request)
# Menerima signal
@receiver(user_logged_in)
def handle_user_login(sender, user, request, **kwargs):
# Logika custom
UserActivity.objects.create(user=user, activity='login')
Contoh Penggunaan Real-world
1. Auto-generate Slug
@receiver(pre_save, sender=Article)
def generate_slug(sender, instance, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.title)
2. Send Email Notification
@receiver(post_save, sender=Order)
def send_order_email(sender, instance, created, **kwargs):
if created:
send_mail(
'Order Confirmation',
f'Your order #{instance.id} has been created.',
'[email protected]',
[instance.user.email],
)
3. Update Cache
@receiver(post_save, sender=Product)
def invalidate_cache(sender, instance, **kwargs):
cache.delete(f'product_{instance.id}')
cache.delete('products_list')
4. Create Related Objects
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
Tips dan Warnings
⚠️ Perhatian
- Signals dapat memperlambat operasi database
- Hindari operasi heavy di dalam signal handlers
- Berhati-hati dengan signal yang memanggil save() lagi
- Signals tidak dipanggil untuk bulk operations (bulk_create, bulk_update)
💡 Tips
- Gunakan
created
parameter di post_save untuk membedakan create vs update - Test signal handlers secara terpisah
- Pertimbangkan menggunakan Celery untuk operasi asynchronous
- Dokumentasikan semua signal handlers dalam proyek
Testing Signals
# test_signals.py
from django.test import TestCase
from django.db.models.signals import post_save
from unittest.mock import patch
class SignalTest(TestCase):
@patch('myapp.signals.send_notification')
def test_signal_called(self, mock_send):
user = User.objects.create(username='test')
mock_send.assert_called_once()
def test_signal_disconnected(self):
# Disconnect signal untuk test ini
post_save.disconnect(create_profile, sender=User)
user = User.objects.create(username='test')
# Assert profile tidak dibuat
self.assertFalse(hasattr(user, 'profile'))
Kesimpulan
Django Signals adalah tool powerful untuk implementasi loosely coupled architecture. Gunakan dengan bijak untuk menjaga performa aplikasi tetap optimal.