﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
3951	Django's signal framework may register listeners more than once	Ben Slavin <benjamin.slavin@…>	Jacob	"= Quick summary =
In the current Django setup, it is possible for a signal listener to register itself with the dispatcher (!PyDispatcher) more than once.

The result is that the '''same function''' can end-up being '''called twice''' when a '''single signal''' is sent.

= Quick example =
== testproj/testapp/models.py ==
{{{
#!python
class A(models.Model):
    a = models.IntegerField(default=1)
def test_function_A(): print ""Called listener""
dispatcher.connect(test_function_A, signal=signals.pre_init, sender=A)
}}}

== Interactive session ({{{./manage.py shell}}}) ==
{{{
#!python
>>> from django.dispatch import dispatcher
>>> from django.db.models import signals

>>> from testproj.testapp.models import A
>>> dispatcher.getReceivers(sender=A, signal=signals.pre_init)
[<function test_function_A at 0x1203fb0>]

>>> from testapp.models import A    
>>> dispatcher.getReceivers(sender=A, signal=signals.pre_init)
[<function test_function_A at 0x1203fb0>, <function test_function_A at 0x1206a30>]

>>> A()
Called listener
Called listener
<A: A object>
}}}

= Deeper discussion =
The problem seems to stem from the fact that Django puts itself in Python's path more than once.  For example, for the project '{{{testproj}}}', Python will look in {{{/path/to/project/}}} and {{{/path/to/project/testproj/}}}.  This, both {{{testapp}}} and {{{testproj.testapp}}} are valid.

Though these locations are both valid and reference the same content, they are actually treated as being different.  '''Models''' seem to be treated as '''singletons''', but '''other objects do not'''.  I assume that this is because of the way in which models are loaded when Django fires-up.

{{{
#!python
>>> from testproj.testapp.models import A as full_path_A_class
>>> from testapp.models import A as short_path_A_class

>>> id(full_path_A_class), id(short_path_A_class)
(6797536, 6797536)

>>> from testproj.testapp.models import test_function_A as full_path_A_function
>>> from testapp.models import test_function_A as short_path_A_function

>>> id(full_path_A_function), id(short_path_A_function)
(18898800, 18960624)
}}}
Note in the last line of output that two different values are returned (18898800 != 18960624).  Thus, the {{{dispatcher.connect}}} function has no way to determine that the listeners are the same.

The code responsible for the registration of listeners in !PyDispatch can be found at {{{django/dispatch/dispatcher.py:153-170}}}; however, I'm not sure that we should go hacking on the !PyDispatcher code.  I'm honestly not sure what the solution to this one should be.


= ""Disclaimers"" =
The following things should be noted:
 * I am using {{{trunk}}} for testing; however, I've also tried manually updating to !PyDispatcher 2.0.0 and the problem still exists.
 * All of my testing has been on Python 2.5.  I assume that older versions will behave similarly.
 * All of my testing has been done in the shell and with the built-in server.
 * It ''is possible'' that this issue does not exist in a FCGI/mod_python deployment... I haven't tested those environments."		closed	Core (Other)	dev		fixed		paul@… ferringb@…	Accepted	0	0	0	0	0	0
