13

I'm trying to immediately update a record after it's saved. This example may seem pointless but imagine we need to use an API after the data is saved to get some extra info and update the record:

def my_handler(sender, instance=False, **kwargs):
    t = Test.objects.filter(id=instance.id)
    t.blah = 'hello'
    t.save()

class Test(models.Model):
    title = models.CharField('title', max_length=200)
    blah = models.CharField('blah', max_length=200)

post_save.connect(my_handler, sender=Test)

So the 'extra' field is supposed to be set to 'hello' after each save. Correct? But it's not working.

Any ideas?

givp
  • 2,494
  • 6
  • 31
  • 30
  • Maybe you could describe how it isn't working? At first blush, it seems like it would make an infinite loop, since post_save calls save, which should invoke post_save, etc. Maybe Django is preventing the recursion? – Ned Batchelder Oct 28 '09 at 23:21
  • I see an infinite loop there. After t.save() a post_save signal is sent, guess which function gets called... – stefanw Oct 28 '09 at 23:22
  • oh, I was under the impression Django wouldn't let the second save trigger post_save again? I guess not. In which case, you are right. It would be an infinite loop. But I'm not seeing the loop or anything. – givp Oct 28 '09 at 23:28
  • This is probably a stupid question but, can't you make the API call first and then save? – Matt Baker Oct 28 '09 at 23:44
  • you mean using pre_save? I have to do it at the model level because I'm not able to modify Django admin in any way for this project. – givp Oct 28 '09 at 23:46
  • Why aren't you just updating the `save` method to do `; self.blah= 'hello'; super( Test, self ).save( *args, **kw )`? What's wrong with overriding `save()`? – S.Lott Oct 29 '09 at 00:00
  • If you're changing a recode `.update()` instead of `save()`. To avoid the loop. As update() doesn't call post_save signal. – Brandon Bertelsen Feb 26 '13 at 14:58

2 Answers2

21

When you find yourself using a post_save signal to update an object of the sender class, chances are you should be overriding the save method instead. In your case, the model definition would look like:

class Test(models.Model):
    title = models.CharField('title', max_length=200)
    blah = models.CharField('blah', max_length=200)

    def save(self, force_insert=False, force_update=False):
        if not self.blah:
            self.blah = 'hello'
        super(Test, self).save(force_insert, force_update)
ozan
  • 9,285
  • 4
  • 30
  • 27
  • 1
    If he's doing this to the admin models, post_save is a better solution than subclassing the existing admin models and overriding save. – Paul McMillan Oct 29 '09 at 00:46
  • `using=False` is needed at 'save' method arguments list in Django 1.3 and higher – Thorin Schiffer Jul 20 '12 at 13:11
  • 1
    Overriding save method isn't always suitable because we don't have primary key until we call save. So if you need to work with PK of an instance, only option seems to be post_save – chhantyal Mar 27 '15 at 11:40
  • 1
    A future-proof way to do override the `save` method while including all the necessary arguments is: `def save(self, *args, **kwargs):` – yndolok Apr 21 '15 at 00:12
6

Doesn't the post_save handler take the instance? Why are you filtering using it? Why not just do:

def my_handler(sender, instance=False, created, **kwargs):
  if created:
     instance.blah = 'hello'
     instance.save()

Your existing code doesn't work because it loops, and Test.objects.filter(id=instance.id) returns a query set, not an object. To get a single object directly, use Queryset.get(). But you don't need to do that here. The created argument keeps it from looping, as it only sets it the first time.

In general, unless you absolutely need to be using post_save signals, you should be overriding your object's save() method anyway.

Paul McMillan
  • 19,693
  • 9
  • 57
  • 71
  • I actually tried that also to no avail. but if the people above are correct, I can't do this anyway since it will just get stuck in a post_save loop. – givp Oct 28 '09 at 23:30
  • Try it using the created flag. I think that should fix your problem, because created is unset on subsequent loops through. – Paul McMillan Oct 28 '09 at 23:31
  • 1
    You might want to look at this question for more info on when to use signals and when to override save: http://stackoverflow.com/questions/170337/django-signals-vs-overriding-save-method – Paul McMillan Oct 28 '09 at 23:34
  • doh! I had the truth value of test for the created flag set wrong for a bit. It should work now... – Paul McMillan Oct 29 '09 at 00:11
  • The original poster doesn't mention he wants the handler to be called only when new record is created. Thus, using the 'created' flag doesn't solve the problem, if the handler must be called also on update. (I'm trying to find a solution to similar problem myself.) – jholster Nov 22 '10 at 12:48
  • jholster: Then trigger on every run, ignoring the `created` argument. – Paul McMillan Nov 22 '10 at 22:44