Django signals are one of those features that sit in an awkward place. On the one hand, they allow decoupling of logic across apps, but they also introduce lines across your project that become difficult to reason about. Most teams I’ve worked on don’t reach for signals first. Not because they’re bad, but because once you introduce them, you lose some visibility and explicitness within your project.
With a function call, you can trace everything. With a signal, you’re relying on registration that may live anywhere in your codebase, often hidden behind imports, app configs, or side effects.
Even worse:
- You don’t always know if a receiver is actually registered
- You can’t easily see all receivers for a signal
- You can’t tell what code is going to run when something fires
I’ve run into all of these issues with signals and it has caused me to steer away from them most of the time.
Some third-party apps define and emit signals that are meant to be extended. Django itself does this with post_save and others. These are useful and well-scoped.
What I’m talking about here is using signals as a first-class pattern throughout your own codebase.
I built dj-signals-panel to make signals more visible for myself. It lives inside the Django admin, like the rest of Django Control Room, and gives you a clear view into your signal system.
It shows:
- All registered Django signals (including internal ones from Django)
- Connected receivers
- Where each receiver lives in your codebase
- Code snippets for each receiver (so you can just inspect what a receiver does from the admin)
I think its more than just convenience; Having signals more visible can change how we develop using signals. In my own work, I’ve often avoided signals unless absolutely necessary. If I had this level of visibility earlier, I probably would’ve used them more.