12

I tested the "pre_save" signal of Django in the following ways, but cannot catch the signal in either of them.

$

from django.db.models.signals import pre_save
import logging

def my_callback(sender, **kwargs):
    logging.debug("======================================")
pre_save.connect(my_callback)
  1. Run the above code in manage.py shell: Then I run my website and see models.save() work successfully, but the callback function does not run.

  2. Alternatively, I run the above code on shell again and then run models.save() in the shell. "save" works well again but still nothing happens to the callback function.

  3. Finally, I embed the above code in an __init__.py file and still, run the save() function on the website. Still, nothing occurs.

Would you please help me figure out why pre_save signal seems not work?

Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
Wei An
  • 1,779
  • 4
  • 13
  • 18

4 Answers4

10

You're not setting the sender class for one.

from django.db.models.signals import pre_save
from myapp.models import MyModel
import logging

def my_callback(sender, **kwargs):
    logging.debug("======================================")
pre_save.connect(my_callback, sender=MyModel)

Secondly, if you're using Django 1.3 you should use the new decorator syntax.

# Inside your models.py
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver

class MyModel(models.Model):
    field1 = models.TextField()
    field2 = models.IntegerField()

@receiver(pre_save, sender=MyModel)
def mymodel_save_handler(sender, **kwargs):
    logging.debug("======================================")

That should do it, but I haven't tested the code so let me know if it's still broken.

Ross
  • 46,186
  • 39
  • 120
  • 173
Eric Palakovich Carr
  • 22,701
  • 8
  • 49
  • 54
  • 8
    You are not required to specify the sender class, nor use the decorator syntax. The reason it didn't work is none of the above, it's because he is attaching the signals in a different thread to where the save is happening. – Anentropic Aug 26 '11 at 09:40
  • Note that for the @receiver code to work you also need to import receiver from django.dispatch – bryn Sep 20 '12 at 03:21
  • For some reason, this code only worked when the signal receiver was in the same model.py where the signal emitter was (model). – Prince Mishra Feb 09 '16 at 11:30
3

logging.debug() is using the root logger, which handler level by default is 30 ('WARNING').

=> logging.debug('something') is just not doing anything at all (DEBUG level is 10 < 30). See http://docs.python.org/2/library/logging.html

The same test should be done with another custom logger, or by doing:

l = logging.getLogger()
l.setLevel(10)
def my_callback(sender, **kwargs):
    logging.debug("======================================")
pre_save.connect(my_callback)

The original question is not containing enough info to know if it's the real problem the OP was facing (or part of it).
But surely the OP's code was not going to work with my ./manage.py shell

Anoyz
  • 7,431
  • 3
  • 30
  • 35
lajarre
  • 4,910
  • 6
  • 42
  • 69
3

The reason Eric's answer made it work is because he got you to connect the signal inside models.py, so that when you then save the model via your website the signal handler is in the same process as the signal firer.

In examples 1 and 3 it's easy to see why they didn't work - you are saving in a different process (the website) to where your signal receivers are listening.

I wish I understood better why example 2 is also broken but I just debugged a similar issue in my own project while testing signals in the shell and it's definitely something to do with signal senders and receivers not being able to 'see' each other.

Anentropic
  • 32,188
  • 12
  • 99
  • 147
  • I have no idea if that's the correct explanation but it is definitely interesting and worth of being criticized or voted up. But I don't see why it'd be a different thread or process... – lajarre Nov 14 '12 at 01:50
  • 1
    it's a different process because one is the manage.py shell session, the other is the manage.py runserver... it's the same codebase but they are running independently so changes made to objects in the shell (connection of signal handler) can't affect the equivalent objects in the web server – Anentropic Nov 14 '12 at 16:02
  • (pretty sure 'process' is the correct term, rather than 'thread' as I used in comment to Eric's answer) – Anentropic Nov 14 '12 at 16:05
  • Yes I think it's right for 1, but not 3 if it's a django app __init__.py (neither 2). Also, this `logging.debug` seems *very* suspicious to me => see my answer – lajarre Nov 14 '12 at 17:59
  • agree about 3, as long as code got reloaded in the web server – Anentropic Nov 14 '12 at 20:17
-1

as described in the django signals doc the pre_save signal accept 3 unique arguments (not keyword arguments) so, you need to edit your my_callback function to be as follow:

def my_callback(sender,instance, using, **kwargs):
    logging.debug("======================================")
MBarsi
  • 2,417
  • 1
  • 18
  • 18
  • Actually those arguments aren't arguments, but values stored inside the kwargs dict. – Eric Palakovich Carr May 27 '11 at 15:02
  • Well, that's actually the same thing. But Django docs do say signal receivers should accept (sender, **kwargs), I guess for future compatibility, rather than naming the specific args of the signal. – Anentropic Aug 26 '11 at 09:39