Is it approproate it use django signals withing the same app
- by Alex Lebedev
Trying to add email notification to my app in the cleanest way possible. When certain fields of a model change, app should send a notification to a user. Here's my old solution:
from django.contrib.auth import User
class MyModel(models.Model):
user = models.ForeignKey(User)
field_a = models.CharField()
field_b = models.CharField()
def save(self, *args, **kwargs):
old = self.__class__.objects.get(pk=self.pk) if self.pk else None
super(MyModel, self).save(*args, **kwargs)
if old and old.field_b != self.field_b:
self.notify("b-changed")
# Sevelar more events here
# ...
def notify(self, event)
subj, text = self._prepare_notification(event)
send_mail(subj, body, settings.DEFAULT_FROM_EMAIL, [self.user.email], fail_silently=True)
This worked fine while I had one or two notification types, but after that just felt wrong to have so much code in my save() method. So, I changed code to signal-based:
from django.db.models import signals
def remember_old(sender, instance, **kwargs):
"""pre_save hanlder to save clean copy of original record into `old` attribute
"""
instance.old = None
if instance.pk:
try:
instance.old = sender.objects.get(pk=instance.pk)
except ObjectDoesNotExist:
pass
def on_mymodel_save(sender, instance, created, **kwargs):
old = instance.old
if old and old.field_b != instance.field_b:
self.notify("b-changed")
# Sevelar more events here
# ...
signals.pre_save.connect(remember_old, sender=MyModel, dispatch_uid="mymodel-remember-old")
signals.post_save.connect(on_mymodel_save, sender=MyModel, dispatch_uid="mymodel-on-save")
The benefit is that I can separate event handlers into different module, reducing size of models.py and I can enable/disable them individually. The downside is that this solution is more code and signal handlers are separated from model itself and unknowing reader can miss them altogether. So, colleagues, do you think it's worth it?