5

I need to do some actions when one field has changed.

Since this action needs to work with already saved object, I can't use pre_save signal like this:

@receiver(pre_save, sender=reservation_models.Reservation)
def generate_possible_pairs(sender, instance, **kwargs):
    try:
        reservation_old = sender.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
    else:
        if not reservation_old.datetime == instance.datetime: # Field has changed
            do_something(instance) # It would be better to be sure instance has been saved

Is it possible to use post_save signal for this?

I would like to avoid adding temporary attributes to this model.

Milano
  • 18,048
  • 37
  • 153
  • 353
  • First (and most import) question: why do you want to use signals here ? (hint: you can customize your model's `save()` method instead). Second question: what makes you think you cannot use `pre_save` for "already saved" objects ? `pre_save` is not `pre_create`, it _is_ called every time your model instance is saved. – bruno desthuilliers Aug 01 '17 at 12:18
  • 1
    1. Although signals can cause painful debugging, it seems to be a more elegant solution. If I did detection in overrided save method, I would have to add multiple (6) additional fields. I have to check for two date and two time attributes (datetime is property), status and whether the object has been created. 2. I thought that if anything happens in pre_save method (exception etc.) it is not saved into db and commited. – Milano Aug 01 '17 at 12:54
  • As far as I'm concerned the most "elegant" solution is the simplest one. Also I don't understand why doing your job in the model's `save()` would require any additional field - you just do the same thing as in your pre_save handler: load the original version from db (before saving anything) and compare both versions (which can be done after saving). wrt/ point 2/, I might have misinterpreted your requirement that it should "work with already saved objects" :) – bruno desthuilliers Aug 01 '17 at 13:18

2 Answers2

12

Using the post_save signal you won't be able to retrieve the previous state from db - But why use a signal at all ?

class Reservation(models.Model):
    def save(self, *args, **kw):
        old = type(self).objects.get(pk=self.pk) if self.pk else None
        super(Reservation, self).save(*args, **kw)
        if old and old.datetime != self.datetime: # Field has changed
            do_something(self)

You may also want to read this : https://lincolnloop.com/blog/django-anti-patterns-signals/

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • 1
    Can you add some comments to explain what your code does? This is not clear without a sample model class. – Nairum Nov 21 '19 at 17:36
  • @AlexeiMarinichenko if you want a sample model you'll have to ask the OP - but obviously their `Reservation` model has a field named `datetime` (bad naming but anyway). Now you don't need a "sample model" here and comments would only paraphrase the code. To sum it up: we're overloading the model's `save()` method to detect wheter the `datetime` field value has changed, and we do it by first loading the current model's state (named `old`) from the database and comparing with the current instance state. No rokect science involved, really... – bruno desthuilliers Nov 25 '19 at 11:08
-5

Yes you can use a post_save too. You should however remember signals are synchronous

phacic
  • 1,402
  • 1
  • 14
  • 21
  • 1
    How can I use post_save? I don't know how to recognize change in post_save since instance has been already saved. – Milano Aug 01 '17 at 12:04
  • pre_save fires before the object is save and post_save after the object is saved so just change the pre_save to post save in the @receiver decorator – phacic Aug 01 '17 at 12:11