Django pre_save Signal

When working with Django, you may need to run custom logic immediately before a model instance is saved to the database. I’ve found Django’s pre_save signal to be a useful tool for this. It allows you to hook into the save process and perform actions such as data validation, auto-populating fields, or even preventing a save under specific conditions.

In this article, I’ll walk you through what the pre_save signal is, why it’s useful, and how to use it effectively with clear, practical examples.

Let’s get in!

What is the Django pre_save Signal?

Django signals are a way to allow decoupled applications to get notified when certain actions occur elsewhere in the framework. The pre_save signal is sent just before a model’s save() method is called, but before the data is written to the database.

This means you can intercept the save process, modify the instance, or even raise errors before the record is saved.

Check out Create a Function-Based View in Django

How to Use Django pre_save Signal

Let me show you the steps to use the Django pre_save signal.

Step 1: Import Necessary Modules

First, you need to import Django’s signal system and your model:

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import Order

Step 2: Define a Signal Receiver Function

This function will run every time before an Order instance is saved.

@receiver(pre_save, sender=Order)
def before_order_save(sender, instance, **kwargs):
    # Example: Automatically set order status to 'Pending' if not set
    if not instance.status:
        instance.status = 'Pending'

    # Example: Validate that the order quantity is positive
    if instance.quantity <= 0:
        raise ValueError("Order quantity must be greater than zero.")

Step 3: Connect the Signal (Optional if using @receiver)

Using the @receiver decorator automatically connects the signal. Alternatively, you can connect manually:

pre_save.connect(before_order_save, sender=Order)

Read Create a Web Form in Python Django

Full Example: Use pre_save in a Real-World US E-commerce Scenario

Imagine you’re building an e-commerce platform serving customers in the US. You want to ensure that, before an order is saved:

  • The order’s status field defaults to "Pending" if not set.
  • The shipping_state is always stored in uppercase (e.g., “CA” for California).
  • The order_total is recalculated based on the quantity and unit price.

Here’s how you can do it with pre_save:

# models.py
from django.db import models

class Order(models.Model):
    customer_name = models.CharField(max_length=100)
    shipping_state = models.CharField(max_length=2)  # US state abbreviation
    quantity = models.PositiveIntegerField()
    unit_price = models.DecimalField(max_digits=8, decimal_places=2)
    order_total = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
    status = models.CharField(max_length=20, blank=True, null=True)

    def __str__(self):
        return f"Order #{self.id} for {self.customer_name}"
# signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Order

@receiver(pre_save, sender=Order)
def before_order_save(sender, instance, **kwargs):
    # Default status to 'Pending' if empty
    if not instance.status:
        instance.status = 'Pending'

    # Ensure shipping_state is uppercase (e.g., CA, NY)
    if instance.shipping_state:
        instance.shipping_state = instance.shipping_state.upper()

    # Validate quantity
    if instance.quantity <= 0:
        raise ValueError("Order quantity must be greater than zero.")

    # Calculate order total
    instance.order_total = instance.quantity * instance.unit_price

Step 4: Register the Signal

Make sure your signal handlers are imported when Django starts. The best practice is to import your signals inside your app’s apps.py:

# apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'myapp'

    def ready(self):
        import myapp.signals

Then update your app’s __init__.py to use this config:

default_app_config = 'myapp.apps.MyAppConfig'
django pre_save
django signals pre_save

Check out Build a Contact Form in Django using Bootstrap

Alternative: Overriding the save() Method vs Using pre_save

I’ve often been asked whether to use pre_save or override the model’s save() method. Here’s my take:

  • Use pre_save if you want to keep model logic clean and allow multiple listeners.
  • Override save() if the logic is tightly coupled with the model, and you want everything in one place.

Both approaches work, but signals provide better separation of concerns.

Read Create a Form with Django Crispy Forms

Things to Keep in Mind

  • pre_save runs every time you save a model instance, including updates.
  • Avoid heavy processing inside signals to prevent slowing down saves.
  • Be careful with exceptions inside signals; raising errors will prevent saving.
  • Always test your signals thoroughly.

Using Django’s pre_save signal has saved me countless hours by automating repetitive tasks and enforcing rules consistently. Once you get the hang of it, you’ll find signals to be a powerful addition to your Django toolkit.

Other Django articles you may also like:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.