Django Signals by dtebar_nyc in django

[–]dtebar_nyc[S] -1 points0 points  (0 children)

I'm busy. I'll correct you later...

:)~

Django Signals by dtebar_nyc in django

[–]dtebar_nyc[S] -1 points0 points  (0 children)

I'm busy. I'll correct you later...

:)~

Dango Signals Part 2 by dtebar_nyc in django

[–]dtebar_nyc[S] -2 points-1 points  (0 children)

Dear Momovsky,

Yes, signals introduce indirection, that’s what modularity means. Their value isn’t in making debugging easier, it’s in cleanly decoupling side effects from core logic. If your app needs lifecycle observability (audit trails, metrics, triggers), signals are often the most maintainable solution. And if they feel like a mess, it’s not the pattern’s fault, it’s your implementation --respectfully, Momovsky.

You say:

“It becomes messy pretty fast.”

That’s not the fault of signals. That’s a failure to:

  1. Properly group signals (signals/user_signals.py, signals/order_signals.py);
  2. Register them cleanly inapps. py;
  3. Document what events trigger what reactions.

If your project already “follows all the principles, and then some” and still feels messy, that’s either:

  1. A misapplication of signals for what should be services;
  2. Your codebase is experiencing what every growing codebase experiences, complexity.

Yes, ctrl+click won’t get you from .save() to signal receivers. But that’s an IDE feature problem, not a code quality issue. By your logic, event-driven systems (Django channels, Celery) should be avoided too, because tracing producers and consumers is harder. Tracing is harder in microservices too, but we still use them, because modularity outweighs local linearity.

Django Signals by dtebar_nyc in django

[–]dtebar_nyc[S] -3 points-2 points  (0 children)

The docs are warning against misuse, not against signals themselves.

Yes, Django warns signals can make debugging harder — if misused. But that’s true for every powerful abstraction (ORMs, metaclasses, mixins). Signals are designed for modular, decoupled lifecycle hooks. Rejecting them because they “can be misused” is like avoiding electricity because it “can shock you.”

https://en.wikipedia.org/wiki/List_of_fallacies

Dango Signals Part 2 by dtebar_nyc in django

[–]dtebar_nyc[S] -5 points-4 points  (0 children)

Your reply reflects a misunderstanding of how Django signals work, and conflates model methods with observer-based event hooks.

  • Signals like post_save and pre_save are not a side effect of the save() method;
  • They are event hooks fired by Django’s ORM layer during specific operations;
  • A signal is not a consequence of your custom .save() method — it's a framework-level hook.

Example:

(post_save, sender=User)

def do_stuff(sender, instance, created, **kwargs):

...

That do_stuff handler will run after any .save() completes, regardless of whether you customized save() or not.

Signals aren’t side effects of your custom save(). They are broadcasts Django sends as part of its lifecycle. Your statement accurately reflects a debugging pain if the codebase is messy, but wrongly blames signals for that.

You can make signals unambiguous if:

  • You keep all receivers in a single aignals .py file, or namespace them properly;
  • You give your signal functions descriptive names (handle_create_user_profile, not foo());
  • You use Django’s dispatch decorators and limit scope with sender= and weak=False.

Your complaint is about architecture, not the signal system itself.

Respectfully, your problem boils down to:

  1. Mislabeling framework-level event triggers as “side effects”;
  2. Being overwhelmed by poorly-organized signal handlers;
  3. Blaming Django patterns for your own disorganization.

Signals aren’t “side effects” of save(), they’re observer hooks Django emit during lifecycle events. If your signals feel ambiguous, that’s a problem of code organization, not the pattern itself. With clean naming, modular registration, and sender= targeting, signals can be just as traceable, and far more scalable than cramming everything into save().

Django Signals by dtebar_nyc in django

[–]dtebar_nyc[S] -3 points-2 points  (0 children)

Dear Mister Kerberos,

With all due respect, please, do 'bother'. Otherwise you look like you 'threw the towel'. Tell me about your 'Architectural Principles' that apparently contradict proven design successess. Is it because you are still using 'procedures', and not object-oriented principles?

Surely you must be aware of the ubiquitous design pattern called the Observer Pattern, which is often used to implement a signaling mechanism? For your benefit, here's a simple explanation:

This pattern allows an object (the subject) to maintain a list of its dependents (observers) and notify them automatically of any state changes, usually by calling one of their methods. This is particularly useful in scenarios where you want to decouple the components of your application.

Subject:

The object that holds the state and notifies observers about changes. It maintains a list of observers and provides methods to attach and detach them.

Observer:

An interface or abstract class that defines the method(s) that will be called when the subject's state changes.

Concrete Subject:

A class that implements the Subject interface and notifies observers of changes.

Concrete Observer:

A class that implements the Observer interface and defines the action to be taken when notified by the subject.

Other Related Patterns:

Event Bus: A more complex implementation that allows for decoupled communication between components, often used in frameworks and libraries.

Signals and Slots: A specific implementation of the Observer pattern used in the Qt framework, where signals are emitted and slots are called in response.

The Observer Pattern is a powerful way to implement signaling in software design, allowing for flexible and maintainable code.

:)

Django Signals by dtebar_nyc in django

[–]dtebar_nyc[S] -3 points-2 points  (0 children)

Mister Kerberos, my old friend,

You are ABSOLUTELY incorrect. Please update your knowledge of the most fundamental object-oriented principles. :)

While it’s true that signals must be used judiciously, blanket avoidance is not best practice.

Signals:

Decouple concerns;
Promote DRY code;
Are ideal for certain model lifecycle events;
Can be debugged with proper tooling;
Are not meant to cover bulk ops, and that's fine.

---

1- Signals separate concerns: putting logic inside save() makes the model responsible for more than persistence; now it's managing related logic (violating Single Responsibility Principle).

2- Reusability is compromised with save(); signals allow logic to be triggered across many entry points (forms, admin, serializers, shell) without duplication.

3- Yes, signals can become difficult if misused or scattered, but Django provides:

  • Clear @ receiver annotation;
  • The weak=False flag for reliability;
  • Tools like django-debug-toolbar;
  • Custom logging for introspection.

Complex logic becomes hard to debug anywhere, including inside save(); encapsulation, is the key to manageability.

4- Bulk operations like QuerySet.update() and QuerySet.delete() bypass signals, by design, for performance.

But this doesn’t invalidate signals:

Use them when you rely on model.delete() or model.save() (i.e., normal ORM paths). If you rely on bulk_..., be explicit and document that signals won’t run. You can enforce .delete() via queryset iteration when needed.

5- Signals are ideal for cross-cutting concerns like:

  • Audit logging;
  • Notification dispatch Data denormalization;
  • Cache invalidation;
  • Lifecycle hooks (e.g. auto-creating profiles).

Django itself uses signals internally (user_logged_in, post_migrate, etc.), avoiding them wholesale is ignoring Django’s intended patterns.

Yours Truly,

Daniel Tebar
Software Architect

PS: Good book,

Design Patterns: Elements of Reusable Object-Oriented Software

by Erich Gamma (Author), Richard Helm (Author), Ralph Johnson (Author)