0
0
DjangoComparisonBeginner · 4 min read

When to Use Signals vs Overriding Save in Django: Key Differences

Use overriding save in Django when you want to customize or validate model behavior directly within the model itself. Use signals when you want to decouple side effects or actions triggered by model events, especially if multiple parts of your app need to react to those events.
⚖️

Quick Comparison

Here is a quick comparison of Django signals and overriding the save method to help you decide which fits your needs.

FactorOverriding saveSignals
PurposeCustomize model behavior directlyReact to model events externally
CouplingTightly coupled to modelLoosely coupled, separate handlers
ComplexitySimple for small changesBetter for multiple listeners or decoupled logic
Order of executionRuns immediately in saveRuns after save, asynchronous possible
Use case exampleValidate or modify fields before savingSend notification after model saved
DebuggingEasier to trace in model codeCan be harder to track across app
⚖️

Key Differences

Overriding the save method means you change the model's own save() function to add or modify behavior when saving an instance. This is direct and clear because the logic lives inside the model class. It is best for changes that are tightly related to the model's data, like setting default values, validating fields, or updating related fields before saving.

Signals are a way to listen for certain events like post_save or pre_delete without changing the model code itself. They allow you to keep side effects separate, such as sending emails, logging, or updating other parts of the system. Signals help keep your code modular and decoupled, especially when multiple components need to react to the same event.

While overriding save() runs synchronously and immediately during the save process, signals run after the save completes and can be connected or disconnected dynamically. However, signals can make debugging harder because the flow is less direct and spread across different files.

⚖️

Code Comparison

Here is how you would use overriding save to automatically set a slug field before saving a blog post.

python
from django.db import models
from django.utils.text import slugify

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)
Output
When saving a BlogPost instance without a slug, the slug is automatically generated from the title.
↔️

Signals Equivalent

Here is how you would use a post_save signal to set the slug after a blog post is saved.

python
from django.db import models
from django.utils.text import slugify
from django.db.models.signals import post_save
from django.dispatch import receiver

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)

@receiver(post_save, sender=BlogPost)
def set_slug(sender, instance, created, **kwargs):
    if created and not instance.slug:
        instance.slug = slugify(instance.title)
        instance.save()
Output
After a new BlogPost is saved, the slug is generated and saved again if it was empty.
🎯

When to Use Which

Choose overriding save() when you want to keep logic simple and directly related to the model's data changes, such as validation or automatic field updates before saving.

Choose signals when you want to keep your model code clean and separate side effects like notifications, logging, or updating other models, especially if multiple parts of your app need to respond to the same event.

In general, prefer overriding save() for core model behavior and signals for decoupled, cross-cutting concerns.

Key Takeaways

Override save() for direct, model-specific data changes before saving.
Use signals to decouple side effects triggered by model events.
Signals run after save and can trigger multiple listeners.
Overriding save is easier to debug and trace.
Choose based on whether logic belongs inside the model or outside it.