Introduction: Why Use post_save
in Django?
In real-world Django applications, you often need to trigger additional actions immediately after saving a model instance — such as:
-
Sending email notifications
-
Updating related models
-
Logging or analytics
-
Indexing to search engines
-
Triggering external APIs
The best way to handle such "after-save" logic is using Django's post_save
signal or custom serializer methods in Django REST Framework (DRF).
By keeping these actions separate from your core business logic, you create cleaner, more maintainable, and decoupled applications.
⚙️ Option 1: Using post_save
Signal in Django Models
Step-by-Step Guide
1. Import Required Modules
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
2. Create the Signal Handler
@receiver(post_save, sender=Order)
def send_order_confirmation(sender, instance, created, **kwargs):
if created:
print(f"Order created for user {instance.user}. Sending confirmation email.")
This handler runs after a new Order
instance is saved and sends a confirmation (or logs the intent).
⚙️ Option 2: Using DRF Serializer create()
or update()
Methods
In DRF, you may want to trigger logic after saving an object via an API endpoint.
Example: Using Serializer Hooks
from rest_framework import serializers
from .models import Order
from .emails import send_confirmation_email
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'user', 'product', 'quantity']
def create(self, validated_data):
# Save the object first
order = super().create(validated_data)
# Then perform post-save actions
send_confirmation_email(order.user.email)
return order
✅ This is ideal when your post-save logic is tightly coupled with API actions and doesn’t need to run in other parts of your app (e.g. admin).
Complete Functional Example
models.py
from django.db import models
from django.contrib.auth.models import User
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
product = models.CharField(max_length=100)
quantity = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.product} x {self.quantity} by {self.user.username}"
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
@receiver(post_save, sender=Order)
def notify_order_created(sender, instance, created, **kwargs):
if created:
print(f"New order for {instance.product}. Sending notification to admin.")
apps.py
from django.apps import AppConfig
class ShopConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'shop'
def ready(self):
import shop.signals # Connects the signal when the app is loaded
serializers.py
from rest_framework import serializers
from .models import Order
from .utils import send_order_email
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'user', 'product', 'quantity']
def create(self, validated_data):
order = super().create(validated_data)
send_order_email(order.user.email, order.product)
return order
Comparison Table: post_save
vs Serializer Logic
Feature | post_save Signal |
Serializer Method |
---|---|---|
Trigger Location | Model-level (global) | API-specific (DRF only) |
Works outside DRF | ✅ Yes | ❌ No |
Decoupled from business logic | ✅ Yes | ❌ Often coupled |
Debugging difficulty | Medium | Easy |
Testing simplicity | Complex (mock signals) | Straightforward |
Tips & Common Pitfalls
✅ Tips
-
Use
post_save
for global triggers like notifications, logging, or updates across the app. -
Use serializers when the logic only applies to API requests.
-
Always import and register signal modules in
apps.py
.
❌ Common Pitfalls
-
Not importing your
signals.py
→ they won’t trigger. -
Triggering infinite loops if a post-save action calls
save()
again. -
Overusing signals for things better handled with business logic or Celery tasks.
Conclusion: Best Practices for post_save
Both Django’s post_save
signals and DRF’s serializer methods help automate post-save actions — but in different contexts.
Key Takeaways
-
Use
post_save
for system-wide actions (e.g., notifications, logging). -
Use serializer logic for request-specific post-processing (e.g., emailing users).
-
Ensure
signals.py
is imported and connected properly.
By understanding the right tool for the right job, you’ll keep your codebase clean, scalable, and easy to debug.