0
# Django Signals
1
2
Django's signal system provides decoupled notifications for actions happening throughout the framework, enabling loose coupling between applications through event-driven programming patterns.
3
4
## Core Imports
5
6
```python
7
# Signal base classes
8
from django.dispatch import Signal, receiver
9
10
# Model signals
11
from django.db.models.signals import (
12
pre_init, post_init, pre_save, post_save,
13
pre_delete, post_delete, m2m_changed,
14
pre_migrate, post_migrate, class_prepared
15
)
16
17
# Core framework signals
18
from django.core.signals import (
19
request_started, request_finished,
20
got_request_exception, setting_changed
21
)
22
23
# Authentication signals
24
from django.contrib.auth.signals import (
25
user_logged_in, user_logged_out, user_login_failed
26
)
27
28
# Database backend signals
29
from django.db.backends.signals import connection_created
30
31
# Test signals
32
from django.test.signals import setting_changed as test_setting_changed
33
```
34
35
## Capabilities
36
37
### Signal Base Classes
38
39
Core signal infrastructure for creating and managing custom signals.
40
41
```python { .api }
42
class Signal:
43
"""
44
Base signal class for creating custom signals.
45
46
Attributes:
47
receivers: List of connected receiver functions
48
lock: Threading lock for receiver management
49
use_caching: Whether to cache receiver lookups
50
sender_receivers_cache: Cache of receivers for specific senders
51
"""
52
receivers: list[Any]
53
lock: threading.Lock
54
use_caching: bool
55
sender_receivers_cache: MutableMapping[Any, Any]
56
57
def __init__(self, use_caching: bool = True) -> None: ...
58
59
def connect(self, receiver: Callable, sender: object | None = None, weak: bool = True, dispatch_uid: Hashable | None = None) -> None:
60
"""
61
Connect receiver function to this signal.
62
63
Args:
64
receiver: Function to call when signal is sent
65
sender: Specific sender to listen for (None for all)
66
weak: Whether to use weak references
67
dispatch_uid: Unique identifier for this connection
68
"""
69
70
def disconnect(self, receiver: Callable | None = None, sender: object | None = None, dispatch_uid: str | None = None) -> bool:
71
"""
72
Disconnect receiver from this signal.
73
74
Args:
75
receiver: Function to disconnect
76
sender: Specific sender to disconnect from
77
dispatch_uid: Unique identifier of connection to remove
78
79
Returns:
80
Whether a receiver was disconnected
81
"""
82
83
def has_listeners(self, sender: Any = None) -> bool:
84
"""Check if signal has any listeners for given sender."""
85
86
def send(self, sender: Any, **named: Any) -> list[tuple[Callable, str | None]]:
87
"""
88
Send signal to all connected receivers.
89
90
Args:
91
sender: Object sending the signal
92
**named: Additional keyword arguments for receivers
93
94
Returns:
95
List of (receiver, response) tuples
96
"""
97
98
def send_robust(self, sender: Any, **named: Any) -> list[tuple[Callable, Exception | Any]]:
99
"""
100
Send signal to all receivers, catching exceptions.
101
102
Args:
103
sender: Object sending the signal
104
**named: Additional keyword arguments for receivers
105
106
Returns:
107
List of (receiver, response_or_exception) tuples
108
"""
109
110
def receiver(signal: Signal | list[Signal] | tuple[Signal, ...], *, sender: object | None = None, weak: bool = True, dispatch_uid: Hashable | None = None) -> Callable:
111
"""
112
Decorator for connecting functions to signals.
113
114
Args:
115
signal: Signal or signals to connect to
116
sender: Specific sender to listen for
117
weak: Whether to use weak references
118
dispatch_uid: Unique identifier for connection
119
120
Returns:
121
Decorator function
122
"""
123
```
124
125
### Model Signals
126
127
Signals sent during model lifecycle events.
128
129
```python { .api }
130
class ModelSignal(Signal):
131
"""
132
Signal subclass for model-related events with enhanced connect/disconnect methods.
133
"""
134
def connect(self, receiver: Callable, sender: type[Model] | str | None = None, weak: bool = True, dispatch_uid: str | None = None, apps: Apps | None = None) -> None:
135
"""
136
Connect receiver to model signal.
137
138
Args:
139
receiver: Function to call when signal is sent
140
sender: Model class or app label to listen for
141
weak: Whether to use weak references
142
dispatch_uid: Unique identifier for connection
143
apps: Apps registry for lazy signal connections
144
"""
145
146
def disconnect(self, receiver: Callable | None = None, sender: type[Model] | str | None = None, dispatch_uid: str | None = None, apps: Apps | None = None) -> bool | None:
147
"""Disconnect receiver from model signal."""
148
149
# Model lifecycle signals
150
pre_init: ModelSignal # Before Model.__init__()
151
post_init: ModelSignal # After Model.__init__()
152
pre_save: ModelSignal # Before Model.save()
153
post_save: ModelSignal # After Model.save()
154
pre_delete: ModelSignal # Before Model.delete()
155
post_delete: ModelSignal # After Model.delete()
156
m2m_changed: ModelSignal # When ManyToManyField changes
157
class_prepared: Signal # When model class is prepared
158
159
# Migration signals
160
pre_migrate: Signal # Before migration
161
post_migrate: Signal # After migration
162
```
163
164
**Model Signal Usage Examples:**
165
166
```python
167
from django.db.models.signals import post_save
168
from django.dispatch import receiver
169
from myapp.models import User
170
171
@receiver(post_save, sender=User)
172
def user_post_save(sender, instance, created, **kwargs):
173
if created:
174
# Handle new user creation
175
send_welcome_email(instance)
176
else:
177
# Handle user update
178
update_search_index(instance)
179
180
# Connect without decorator
181
def my_handler(sender, **kwargs):
182
pass
183
184
post_save.connect(my_handler, sender=User)
185
```
186
187
### Core Framework Signals
188
189
Signals for core Django framework events.
190
191
```python { .api }
192
# Request/response cycle signals
193
request_started: Signal # When Django starts processing request
194
request_finished: Signal # When Django finishes processing request
195
got_request_exception: Signal # When request processing raises exception
196
197
# Configuration signals
198
setting_changed: Signal # When Django setting is changed
199
```
200
201
**Core Signal Usage Examples:**
202
203
```python
204
from django.core.signals import request_started
205
from django.dispatch import receiver
206
207
@receiver(request_started)
208
def my_request_started_handler(sender, environ, **kwargs):
209
# Handle request start
210
log_request_started(environ)
211
212
# Settings change handler
213
@receiver(setting_changed)
214
def setting_changed_handler(sender, setting, value, enter, **kwargs):
215
if setting == 'DEBUG':
216
configure_logging(value)
217
```
218
219
### Authentication Signals
220
221
Signals for user authentication events.
222
223
```python { .api }
224
# Authentication events (from django.contrib.auth.signals)
225
user_logged_in: Signal # When user logs in
226
user_logged_out: Signal # When user logs out
227
user_login_failed: Signal # When login attempt fails
228
```
229
230
**Authentication Signal Usage Examples:**
231
232
```python
233
from django.contrib.auth.signals import user_logged_in
234
from django.dispatch import receiver
235
236
@receiver(user_logged_in)
237
def user_logged_in_handler(sender, request, user, **kwargs):
238
# Track user login
239
LoginHistory.objects.create(
240
user=user,
241
ip_address=request.META.get('REMOTE_ADDR'),
242
timestamp=timezone.now()
243
)
244
```
245
246
### Database Backend Signals
247
248
Signals for database connection events.
249
250
```python { .api }
251
# Database backend events
252
connection_created: Signal # When database connection is created
253
```
254
255
### Custom Signal Creation
256
257
Creating and using custom signals for application-specific events.
258
259
```python { .api }
260
# Creating custom signals
261
from django.dispatch import Signal
262
263
# Custom signal with specific arguments
264
order_completed = Signal()
265
266
# Signal with documented arguments
267
payment_processed = Signal() # providing_args=['amount', 'payment_method']
268
```
269
270
**Custom Signal Usage Examples:**
271
272
```python
273
from django.dispatch import Signal, receiver
274
275
# Define custom signal
276
order_completed = Signal()
277
278
# Send signal
279
def complete_order(order):
280
# Complete order logic
281
order.status = 'completed'
282
order.save()
283
284
# Send signal
285
order_completed.send(
286
sender=order.__class__,
287
instance=order,
288
total=order.total,
289
user=order.user
290
)
291
292
# Handle custom signal
293
@receiver(order_completed)
294
def handle_order_completion(sender, instance, total, user, **kwargs):
295
# Send confirmation email
296
send_order_confirmation(user.email, instance)
297
298
# Update inventory
299
update_inventory_for_order(instance)
300
301
# Analytics tracking
302
track_order_completion(user, total)
303
```
304
305
## Advanced Signal Patterns
306
307
### Conditional Signal Handling
308
309
```python
310
@receiver(post_save, sender=User)
311
def conditional_handler(sender, instance, created, **kwargs):
312
# Only handle specific conditions
313
if created and instance.is_active:
314
send_activation_email(instance)
315
```
316
317
### Signal Disconnection
318
319
```python
320
# Temporary disconnection
321
from django.db.models.signals import post_save
322
323
def bulk_create_users(user_data_list):
324
# Disconnect signal to avoid sending emails during bulk creation
325
post_save.disconnect(send_welcome_email, sender=User)
326
327
try:
328
User.objects.bulk_create([
329
User(**data) for data in user_data_list
330
])
331
finally:
332
# Reconnect signal
333
post_save.connect(send_welcome_email, sender=User)
334
```
335
336
### Robust Signal Handling
337
338
```python
339
# Handle exceptions in signal receivers
340
from django.db.models.signals import post_save
341
import logging
342
343
logger = logging.getLogger(__name__)
344
345
@receiver(post_save, sender=User)
346
def robust_post_save_handler(sender, instance, **kwargs):
347
try:
348
# Potentially failing operation
349
external_api_call(instance)
350
except Exception as e:
351
# Log error but don't break the main flow
352
logger.error(f"Failed to sync user {instance.id}: {e}")
353
```
354
355
### Many-to-Many Signal Handling
356
357
```python
358
from django.db.models.signals import m2m_changed
359
from django.dispatch import receiver
360
361
@receiver(m2m_changed, sender=User.groups.through)
362
def groups_changed(sender, instance, action, pk_set, **kwargs):
363
if action == 'post_add':
364
# Handle groups added to user
365
for group_id in pk_set:
366
assign_group_permissions(instance, group_id)
367
elif action == 'post_remove':
368
# Handle groups removed from user
369
for group_id in pk_set:
370
revoke_group_permissions(instance, group_id)
371
```
372
373
## Signal Best Practices
374
375
1. **Keep signal handlers lightweight** - Avoid heavy processing in signal handlers
376
2. **Use robust error handling** - Signal exceptions can break the main application flow
377
3. **Be careful with database operations** - Signal handlers run in the same transaction
378
4. **Document signal dependencies** - Make signal relationships clear
379
5. **Test signal behavior** - Include signal testing in your test suite
380
6. **Use dispatch_uid for testing** - Prevents duplicate connections during tests
381
382
## Testing Signals
383
384
```python
385
from django.test.utils import override_settings
386
from django.db.models.signals import post_save
387
388
class SignalTestCase(TestCase):
389
def test_signal_handler(self):
390
# Track signal calls
391
signal_calls = []
392
393
def test_handler(sender, **kwargs):
394
signal_calls.append(kwargs)
395
396
post_save.connect(test_handler, sender=User)
397
398
try:
399
user = User.objects.create(username='test')
400
self.assertEqual(len(signal_calls), 1)
401
self.assertEqual(signal_calls[0]['instance'], user)
402
finally:
403
post_save.disconnect(test_handler, sender=User)
404
```