or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin.mdauth.mdcontrib.mddatabase-orm.mdforms.mdhttp.mdindex.mdmigrations.mdmypy-plugin.mdsignals.mdtemplates.mdtransactions.mdurls.mdviews.md

signals.mddocs/

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

```