11

I use the pre_save and post_save signals to send analytics to Mixpanel. I prefer to keep this separated from my model's save method.

Is there a way to save the old values of an instance when the pre_save signal occurs, and then check the new values against them on post_save?

My code looks like this:

@receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
    activity_completed_old_value = kwargs['instance'].is_completed
    # store this value somewhere?

@receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
    if kwargs['instance'].is_completed != activity_completed_old_value:
        # send analytics

For me it seems more robust to use post_save to send the analytics than pre_save, but at that point I can't see what has changed in the model instance. I would like to prevent using globals or implementing something in my model's save function.

JacobF
  • 2,305
  • 3
  • 24
  • 36

1 Answers1

10

You can store them as instance attributes.

@receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
    instance = kwargs['instance']
    instance._activity_completed_old_value = instance.is_completed

@receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
    instance = kwargs['instance']     
    if instance.is_completed != instance._activity_completed_old_value:
        # send analytics

In this way you "send analytics" only if is_completed changes during save (that means that save doesn't just store the value but makes some further elaboration).

If you want to perform an action when a field is changed during instance life-time (that is from its creation till the save) you should store the initial value during post_init (and not pre_save).

Don
  • 16,928
  • 12
  • 63
  • 101
  • I was already thinking of that, but is that good practice for Django? And why would I make it a protected attribute? Thanks for the post_init hint, that's better indeed. – JacobF Feb 17 '14 at 16:00
  • @jvannistelrooy: no need for a protected attribute, but it is better you do not use these additional attributes outside those functions, 'cause they are just service fields. I've found this solution somewhere... I'll post the link as soon as I find it – Don Feb 17 '14 at 17:00
  • 1
    Could you please also explain why `post_init` is better than `pre_save`? Wouldn't it add extra db queries even when the value is not actually needed? – Ivan Anishchuk Jul 30 '15 at 18:29
  • 2
    I tried this, but `Activity` instance does not have `_activity_completed_old_value` by the time I am in `post_save` when I try to save model – Sashko Lykhenko Sep 18 '18 at 22:21