From 9084cbe36f51fd8fb3f049ef45a36ea806385393 Mon Sep 17 00:00:00 2001
From: Alexander Artemenko <svetlyak.40wt@gmail.com>
Date: Sun, 5 Oct 2008 20:58:20 +0400
Subject: [PATCH] Propagate message to parent's handler sender is child model.
---
django/dispatch/dispatcher.py | 26 +++++++++++++++----
.../dispatch/tests/test_dispatcher.py | 24 ++++++++++++++++--
2 files changed, 41 insertions(+), 9 deletions(-)
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index 74fdd20..b894b82 100644
a
|
b
|
|
| 1 | import inspect |
1 | 2 | import weakref |
2 | 3 | try: |
3 | 4 | set |
… |
… |
def _make_id(target):
|
13 | 14 | return (id(target.im_self), id(target.im_func)) |
14 | 15 | return id(target) |
15 | 16 | |
| 17 | def _make_ids(target): |
| 18 | '''This function tries to collect parents for |
| 19 | classes, inherited from Model. |
| 20 | Without it, sending signals with inherited |
| 21 | model does as 'sender' does not call appropriate |
| 22 | receiver. |
| 23 | ''' |
| 24 | ids = [_make_id(target)] |
| 25 | |
| 26 | if inspect.isclass(target) and getattr(target, '_meta', None) is not None: |
| 27 | for parent, _ in target._meta.parents.iteritems(): |
| 28 | ids.append(_make_id(parent)) |
| 29 | return ids |
| 30 | |
16 | 31 | class Signal(object): |
17 | 32 | """Base class for all signals |
18 | 33 | |
… |
… |
class Signal(object):
|
65 | 80 | |
66 | 81 | # If DEBUG is on, check that we got a good receiver |
67 | 82 | if settings.DEBUG: |
68 | | import inspect |
69 | 83 | assert callable(receiver), "Signal receivers must be callable." |
70 | 84 | |
71 | 85 | # Check for **kwargs |
… |
… |
class Signal(object):
|
144 | 158 | if not self.receivers: |
145 | 159 | return responses |
146 | 160 | |
147 | | for receiver in self._live_receivers(_make_id(sender)): |
| 161 | for receiver in self._live_receivers(_make_ids(sender)): |
148 | 162 | response = receiver(signal=self, sender=sender, **named) |
149 | 163 | responses.append((receiver, response)) |
150 | 164 | return responses |
… |
… |
class Signal(object):
|
173 | 187 | |
174 | 188 | # Call each receiver with whatever arguments it can accept. |
175 | 189 | # Return a list of tuple pairs [(receiver, response), ... ]. |
176 | | for receiver in self._live_receivers(_make_id(sender)): |
| 190 | for receiver in self._live_receivers(_make_ids(sender)): |
177 | 191 | try: |
178 | 192 | response = receiver(signal=self, sender=sender, **named) |
179 | 193 | except Exception, err: |
… |
… |
class Signal(object):
|
182 | 196 | responses.append((receiver, response)) |
183 | 197 | return responses |
184 | 198 | |
185 | | def _live_receivers(self, senderkey): |
| 199 | def _live_receivers(self, senderkeys): |
186 | 200 | """Filter sequence of receivers to get resolved, live receivers |
187 | 201 | |
188 | 202 | This checks for weak references |
189 | 203 | and resolves them, then returning only live |
190 | 204 | receivers. |
191 | 205 | """ |
192 | | none_senderkey = _make_id(None) |
| 206 | senderkeys.append(_make_id(None)) |
193 | 207 | |
194 | 208 | for (receiverkey, r_senderkey), receiver in self.receivers: |
195 | | if r_senderkey == none_senderkey or r_senderkey == senderkey: |
| 209 | if r_senderkey in senderkeys: |
196 | 210 | if isinstance(receiver, WEAKREF_TYPES): |
197 | 211 | # Dereference the weak reference. |
198 | 212 | receiver = receiver() |
diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py
index baaae9c..8c29731 100644
a
|
b
|
|
| 1 | from django.db import models |
1 | 2 | from django.dispatch import Signal |
2 | 3 | import unittest |
3 | 4 | import copy |
… |
… |
class Callable(object):
|
26 | 27 | |
27 | 28 | a_signal = Signal(providing_args=["val"]) |
28 | 29 | |
| 30 | class Parent(models.Model): pass |
| 31 | class Child(Parent): pass |
| 32 | |
| 33 | |
29 | 34 | class DispatcherTests(unittest.TestCase): |
30 | 35 | """Test suite for dispatcher (barely started)""" |
31 | 36 | |
| 37 | def tearDown(self): |
| 38 | a_signal.receivers = [] |
| 39 | |
32 | 40 | def _testIsClean(self, signal): |
33 | 41 | """Assert that everything has been cleaned up automatically""" |
34 | 42 | self.assertEqual(signal.receivers, []) |
35 | | |
36 | | # force cleanup just in case |
37 | | signal.receivers = [] |
38 | 43 | |
39 | 44 | def testExact(self): |
40 | 45 | a_signal.connect(receiver_1_arg, sender=self) |
… |
… |
class DispatcherTests(unittest.TestCase):
|
103 | 108 | a_signal.disconnect(fails) |
104 | 109 | self._testIsClean(a_signal) |
105 | 110 | |
| 111 | def testPropagateToParent(self): |
| 112 | a_signal.connect(receiver_1_arg, Parent) |
| 113 | |
| 114 | responses = dict(a_signal.send(sender=Child, val='test')) |
| 115 | self.assertEqual(1, len(responses)) |
| 116 | self.assertEqual('test', responses[receiver_1_arg]) |
| 117 | |
| 118 | def testNotPropagateToChild(self): |
| 119 | a_signal.connect(receiver_1_arg, Child) |
| 120 | |
| 121 | responses = dict(a_signal.send(sender=Parent, val='test')) |
| 122 | self.assertEqual(0, len(responses)) |
| 123 | |
106 | 124 | def getSuite(): |
107 | 125 | return unittest.makeSuite(DispatcherTests,'test') |
108 | 126 | |