Django signals are a powerful tool for decoupling logic in your application. The pre_save and post_save signals, in particular, allow developers to execute custom actions before or after saving a model instance to the database. These signals are ideal for automating tasks, reducing manual intervention, and maintaining cleaner code.

In this blog post, we’ll explore how to use these signals effectively by presenting a real-world case study: managing warehouse inventory and sending low stock notifications.

Real-World Case Study: Smart Warehouse Inventory Management

The Scenario

Imagine you’re building an inventory management system for a warehouse. Each time a product is added or updated:
 

  1. The total product count in the warehouse must be updated automatically.
     
  2. If the product quantity falls below a threshold, the warehouse owner must be notified via email to prevent stockouts.
     

This workflow ensures the warehouse runs efficiently, eliminates manual tracking, and prevents inventory shortages.

Implementation

Step 1: Define the Models

We’ll start by creating two models:
 

  1. Warehouse: Stores the total count of all products.
     
  2. Product: Represents individual products linked to a warehouse.
     

from django.db import models

class Warehouse(models.Model):
    name = models.CharField(max_length=255)
    total_products = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=255)
    quantity = models.PositiveIntegerField(default=0)
    warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE, related_name="products")

    def __str__(self):
        return self.name

 

Step 2: Connect Signals for Automation

We’ll use two signals:
 

  1. pre_save to adjust the warehouse’s total product count before saving.
     
  2. post_save to send an email notification if the product quantity is low.
     

from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from .models import Product, Warehouse

@receiver(pre_save, sender=Product)
def update_total_products(sender, instance, **kwargs):
    if instance.pk:
        # Fetch the old quantity from the database
        old_quantity = Product.objects.get(pk=instance.pk).quantity
        difference = instance.quantity - old_quantity
    else:
        difference = instance.quantity

    # Update the warehouse's total product count
    instance.warehouse.total_products += difference
    instance.warehouse.save()

@receiver(post_save, sender=Product)
def send_low_stock_notification(sender, instance, **kwargs):
    low_stock_threshold = 10  # Low stock threshold
    if instance.quantity < low_stock_threshold:
        # Send an email notification to the owner
        send_mail(
            subject=f"Low Stock Alert: {instance.name}",
            message=f"The product '{instance.name}' is running low with only {instance.quantity} items remaining. Please restock soon.",
            from_email="joe@glinteco.com",
            recipient_list=["joe@glinteco.com"],  # Replace with the warehouse owner's email
        )

Step 3: Register the Signals

Ensure the signals are registered when the app is ready by importing them in the apps.py file.

# apps.py
from django.apps import AppConfig

class InventoryConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "inventory"

    def ready(self):
        import inventory.signals  # Import signals

Testing the System

Add a New Product

Create a new product and verify the warehouse's total product count updates automatically.

warehouse = Warehouse.objects.create(name="Main Warehouse")
product = Product.objects.create(name="Widget", quantity=20, warehouse=warehouse)
print(warehouse.total_products)  # Output: 20

Update Product Quantity

Update the product quantity and ensure the warehouse total reflects the change.

product.quantity = 35
product.save()
print(warehouse.total_products)  # Output: 35

Low Stock Notification

Reduce the product quantity below the threshold and check for an email notification.

product.quantity = 5
product.save()
# Email sent: "The product 'Widget' is running low with only 5 items remaining."

Benefits of This Approach

  1. Automation

    Warehouse totals and low stock alerts are handled automatically, eliminating the need for manual updates.
     

  2. Proactive Notifications

    Owners are alerted about low stock before it becomes a critical issue, ensuring smooth operations.
     

  3. Decoupled Logic

    Signals keep the inventory logic separate from the core application, making the system more modular and maintainable.

Best Practices for Using Django Signals

  1. Avoid Complex Logic

    Keep signal handlers simple. For heavy tasks like sending emails, consider using a background task queue like Celery.
     

  2. Minimize Signal Overhead

    Ensure signals don’t perform unnecessary database queries or slow down request handling.
     

  3. Test Signal Behavior

    Test thoroughly to ensure signals trigger as expected and don’t interfere with other parts of the application.

Conclusion

Django’s pre_save and post_save signals are excellent tools for automating workflows and keeping your application logic clean. In this case study, we’ve shown how signals can streamline inventory management by automatically updating warehouse totals and notifying owners of low stock.

By implementing these techniques, you can build smarter, more efficient systems that save time and prevent costly errors.

Ready to optimize your Django app? Start leveraging signals today!