Categories
Django Python

Saving within a post_save signal in Django

One of the more useful features of the Django framework is it’s extensive signaling capabilities. The ORM throws off a handful of signals every time a model is initialized, modified, saved, or deleted. They include:

  • pre_init
  • post_init
  • pre_save
  • post_save
  • pre_delete
  • post_delete
  • m2m_changed
  • class_prepared

I tend to use the post_save signal fairly often as a good way to get around overriding the default save method on models. Recently though I ran into an issue where I was hitting the “maximum recursion depth exceeded” error when I was saving the current model from within the post_save signal. If you think about it, that makes a lot of sense. You save once, then save again in the signal and then it triggers the signal again. BOOM, infinite loop.

To get around the saving within a post_save signal problem, you just need to disconnect the post_save signal before you call save. After save, you can re-connect it.

from django.db.models import signals
signals.post_save.disconnect(some_method, sender=SomeModel)
some_instance.save()
signals.post_save.connect(some_method, sender=SomeModel)

By Jack Slingerland

Founder of Kernl.us. Working and living in Raleigh, NC. I manage a team of software engineers and work in Python, Django, TypeScript, Node.js, React+Redux, Angular, and PHP. I enjoy hanging out with my wife and son, lifting weights, and advancing Kernl.us in my free time.

5 replies on “Saving within a post_save signal in Django”

Dang. That seems really gross! But it’s a solution for me for now!

A lot of others have suggested that if your post_save is having to update data on a field that is should be done in the save method on the model and not in a post_save signal.. What do you think?

It think disconnecting signals can cause concurrency problems. Say user A saves an object, the post_save function is called and the signal is temporarily disabled. At the same time user B saves another object and the function is not called eventhough it should.

A bit late to the party, but just use queryset.update() instead. This will not fire the post-save signal.

There are valid use cases for saving an instance in a post-save. In our example, we import tons of logs, and depending on their status, we create tickets in an external application and update the local record with the ticket number. Since the external application is a bit on the slow side, we create the tickets and update our local records in a post-save signal, rather than doing it synchronously.

Granted, some sort of job queue (e.g. rabbitmq) would be a cleaner solution.

Comments are closed.